<commit_before>/**
 * @file index.js
 * XSD Schema to HTML5 Web Form
 * @author George Bouris <gb@eworx.gr>
 * @copyright Copyright (C) 2017 EEA, Eworx, George Bouris. All rights reserved.
 */

'use strict';

// import heapdump from 'heapdump';  //debug library
import fs from 'fs';
import path from 'path';
import ncp from 'ncp';
import rimraf from 'rimraf';
import openurl from 'openurl';
import express from 'express';
import XSDWebFormParser from './lib/xsdwebform.parser.js'
import XSDWebFormParserError from './lib/xsdwebform.parser.error.js'

/**
 * Class XSDWebForm
 * XSD Schema to HTML5
 */
export default class XSDWebForm {

	/**
	 * Class constructor
	 * Check for arguments
	 */
	constructor(args) {
		return new Promise ( (resolve, reject) => {

			//logging
			this.showLog = true;
			this.verbose = true;
			this.showXSDLog = true;

			//Build directory
			this.buildPath = "build/";
			// Input file variable
			var xsdFile = null;
			// HTML Input file variable
			var xmlHtmlFile = null;
			// Open the default browser when build is completed
			this.autoOpenOutput = false;

			// Check for [-f file] [-a | open browser after build] input
			args.forEach(
				(item, index) => {
					if (item === '-f') {
						xsdFile = args[index + 1];
						return;
					}
					if (item === '-ul') {
						this.showLog = false;
						this.verbose = false;
						return;
					}
					if (item === '-l') {
						this.showXSDLog = false;
						return;
					}
					if (item === '-a') {
						this.autoOpenOutput = true;
						return;
					}
				});

			// XSDWebFormParser
			this.parser = new XSDWebFormParser(this.showLog, this.verbose, this.showXSDLog);

			// If not file input
			if (!xsdFile) {
				xsdFile = "./test/test.xsd";
			}

			this.baseFileName = path.basename(xsdFile);
			this.baseFileName = this.baseFileName.substring(0, this.baseFileName.length - 3);
			this.basePath = path.dirname(xsdFile);
			xmlHtmlFile = this.baseFileName + "form.xml";

			this.prepareJSFiles().then((res) => {
				try {
					this.parseFiles(xsdFile, xmlHtmlFile, this.basePath).then ( (response) => {
						var app = express();
						var parent = this;
						var filePath = path.join( __dirname.substring(0, __dirname.length - 3), this.buildPath);
						app.use(express.static(filePath))
							.listen(3001, function () {
								if (parent.showLog) {
									console.log("\x1b[1m\x1b[37mTest web server is listening on port 3001\x1b[0m");
									console.log(`http://localhost:3001/${parent.baseFileName}html\n\n`);
								}
								resolve(parent);
							});
						});
				} catch (err) {
					XSDWebFormParserError.reportError(err);
					reject(this);
				}
			});
		});
	};

	/**
	 * parseFiles - Parse XSD and XML files
	 * Open .xsd and .form.xml files and read the contents
	 * @param xsdFile
	 * @param xmlHtmlFile
	 */
	parseFiles(xsdFile, xmlHtmlFile, filePath) {
		return new Promise ( (resolve, reject) => {
			var xObject = {
				xfile: xsdFile,
				xdata: '',
				hfile: xmlHtmlFile,
				hdata: ''
			};

			this.getFile(xsdFile).then((res) => {
				xObject.xdata = res;
				this.getFile(filePath + "/" + xmlHtmlFile).then((res) => {
					xObject.hdata = res;
					// Parse file content
					this.parser.parse(xObject);
					// Create HTML file
					this.createFile(this.buildPath  + this.baseFileName + "html", this.getHeader() + this.parser.getHTMLOutput() + this.getFooter() ). then ( () => {
						this.getFile(__dirname + "/ct-codelists-en.json").then((langs) => {
							langs = JSON.parse(langs);
							langs.CTCodelists.Languages.item.forEach((item) => {
								this.createFile(this.buildPath + "lng/" + this.baseFileName + item.code + ".lang.json", this.parser.getFullTextContent(), false);
								resolve();
							});
						});
					});
					// Open browser
					if (this.autoOpenOutput)
						openurl.open(`http://localhost:3001/${this.baseFileName}html`);
				});
			});
		});
	}

	/**
	 * prepareJSFiles - Copy JS folder to build
	 */
	prepareJSFiles() {
		var parent = this;
		return new Promise((resolve, reject) => {
			rimraf(parent.buildPath, fs, function() {
				if (!fs.existsSync(parent.buildPath)) {
					fs.mkdirSync(parent.buildPath);
					fs.mkdirSync(parent.buildPath + 'lng');
					ncp(__dirname + "/test/", parent.buildPath + "test", function(err) {
						if (err) {
							console.error(err);
							reject(err);
						}
					});
					ncp(__dirname + "/assets/", parent.buildPath + "assets", function(err) {
						if (err) {
							console.error(err);
							reject(err);
						}
						ncp(__dirname + "/webform.js", parent.buildPath + parent.baseFileName + "webform.js", function(err) {
							if (err) {
								console.error(err);
								reject(err);
							}
							ncp(__dirname + "/webform.css", parent. buildPath + parent.baseFileName + "webform.css", function(err) {
								if (err) {
									console.error(err);
									reject(err);
								}
								ncp(__dirname + "/ct-codelists-en.json", parent. buildPath  + "ct-codelists-en.json", function(err) {
									if (err) {
										console.error(err);
										reject(err);
									}
									resolve();
								});
							});
						});
					});
				}
			});
		});
	}

	/**
	 * setHeaer
	 * @param pageTitle
	 */

	getHeader() {

		return `
<!DOCTYPE html>

<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>{{'pagetitle' | translate}}</title>

<link rel="stylesheet" type="text/css" href="./assets/css/webform.all.min.css"/>
<link rel="stylesheet" type="text/css" href="./${this.baseFileName}webform.css"/>

<link rel="shortcut icon" type="image/x-icon" href="./assets/img/favicon.ico"/>

<eea-header></eea-header>

EEA

Web Form

	<eea-language data="codeLists" lang="{{selectedLanguage}}" chooselanguage="{{'chooselanguage' | translate}}"></eea-language>

#

{{'formtitle' | translate}}

	`;
	}

	/**
	 * getFooter
	 */
	getFooter() {

		return `

<eea-toolbar scp="this" off="{{'off' | translate}}" on="{{'on' | translate}}" save="{{'save' | translate}}" printpreview="{{'printpreview' | translate}}" close="{{'close' | translate}}" validation="{{'validation' | translate}}"></eea-toolbar>

		<eea-form-build date="${new Date()}"></eea-form-build>

`;
	}

	/**
	 * getFile
	 * @param file
	 */
	getFile(file) {
		return new Promise((resolve, reject) => {
			fs.readFile(file, 'utf8', (err, data) => {
				if (err) {
					XSDWebFormParserError.reportError(`No such file or directory ${err.path}\n`);
					reject(err);
				}
				resolve(data);
			});
		});
	}

	/**
	 * createFile - Save output file
	 * @param filename
	 * @param content
	 */
	createFile(filename, content, log = true) {
		var parent = this;
		return new Promise((resolve, reject) => {
			fs.writeFile(filename, content, function(err) {
				if (err) {
					console.log(err);
					reject(err);
				} else {
					if (parent.showLog && log)
						console.log(`\x1b[2m\x1b[36mThe file ${filename} was saved\x1b[0m\n`);
					resolve();
				}
			});
		});
	}
}

var xsdWebForm = new XSDWebForm(process.argv);

module.exports.xsdWebForm = xsdWebForm;

// heapdump.writeSnapshot('./' + Date.now() + '.heapsnapshot');<commit_msg>Added multiple lang files creation based on ct-codelists-en.json<commit_after>/**
 * @file index.js
 * XSD Schema to HTML5 Web Form
 * @author George Bouris <gb@eworx.gr>
 * @copyright Copyright (C) 2017 EEA, Eworx, George Bouris. All rights reserved.
 */

'use strict';

// import heapdump from 'heapdump';  //debug library
import fs from 'fs';
import path from 'path';
import ncp from 'ncp';
import rimraf from 'rimraf';
import openurl from 'openurl';
import express from 'express';
import XSDWebFormParser from './lib/xsdwebform.parser.js'
import XSDWebFormParserError from './lib/xsdwebform.parser.error.js'

/**
 * Class XSDWebForm
 * XSD Schema to HTML5
 */
export default class XSDWebForm {

	/**
	 * Class constructor
	 * Check for arguments
	 */
	constructor(args) {
		return new Promise ( (resolve, reject) => {

			//logging
			this.showLog = true;
			this.verbose = true;
			this.showXSDLog = true;

			//Build directory
			this.buildPath = "build/";
			// Input file variable
			var xsdFile = null;
			// HTML Input file variable
			var xmlHtmlFile = null;
			// Open the default browser when build is completed
			this.autoOpenOutput = false;

			// Check for [-f file] [-a | open browser after build] input
			args.forEach(
				(item, index) => {
					if (item === '-f') {
						xsdFile = args[index + 1];
						return;
					}
					if (item === '-ul') {
						this.showLog = false;
						this.verbose = false;
						return;
					}
					if (item === '-l') {
						this.showXSDLog = false;
						return;
					}
					if (item === '-a') {
						this.autoOpenOutput = true;
						return;
					}
				});

			// XSDWebFormParser
			this.parser = new XSDWebFormParser(this.showLog, this.verbose, this.showXSDLog);

			// If not file input
			if (!xsdFile) {
				xsdFile = "./test/test.xsd";
			}

			this.baseFileName = path.basename(xsdFile);
			this.baseFileName = this.baseFileName.substring(0, this.baseFileName.length - 3);
			this.basePath = path.dirname(xsdFile);
			xmlHtmlFile = this.baseFileName + "form.xml";

			this.prepareJSFiles().then((res) => {
				try {
					this.parseFiles(xsdFile, xmlHtmlFile, this.basePath).then ( (response) => {
						var app = express();
						var parent = this;
						var filePath = path.join( __dirname.substring(0, __dirname.length - 3), this.buildPath);
						app.use(express.static(filePath))
							.listen(3001, function () {
								if (parent.showLog) {
									console.log("\x1b[1m\x1b[37mTest web server is listening on port 3001\x1b[0m");
									console.log(`http://localhost:3001/${parent.baseFileName}html\n\n`);
								}
								resolve(parent);
							});
						});
				} catch (err) {
					XSDWebFormParserError.reportError(err);
					reject(this);
				}
			});
		});
	};

	/**
	 * parseFiles - Parse XSD and XML files
	 * Open .xsd and .form.xml files and read the contents
	 * @param xsdFile
	 * @param xmlHtmlFile
	 */
	parseFiles(xsdFile, xmlHtmlFile, filePath) {
		return new Promise ( (resolve, reject) => {
			var xObject = {
				xfile: xsdFile,
				xdata: '',
				hfile: xmlHtmlFile,
				hdata: ''
			};

			this.getFile(xsdFile).then((res) => {
				xObject.xdata = res;
				this.getFile(filePath + "/" + xmlHtmlFile).then((res) => {
					xObject.hdata = res;
					// Parse file content
					this.parser.parse(xObject);
					// Create HTML file
					this.createFile(this.buildPath  + this.baseFileName + "html", this.getHeader() + this.parser.getHTMLOutput() + this.getFooter() ). then ( () => {
						this.getFile(__dirname + "/ct-codelists-en.json").then((langs) => {
							langs = JSON.parse(langs);
							let langData = this.parser.getFullTextContent();
							langs.CTCodelists.Languages.item.forEach((item) => {
								this.createFile(this.buildPath + "lng/" + this.baseFileName + item.code + ".lang.json", langData, false);
								resolve();
							});
						});
					});
					// Open browser
					if (this.autoOpenOutput)
						openurl.open(`http://localhost:3001/${this.baseFileName}html`);
				});
			});
		});
	}

	/**
	 * prepareJSFiles - Copy JS folder to build
	 */
	prepareJSFiles() {
		var parent = this;
		return new Promise((resolve, reject) => {
			rimraf(parent.buildPath, fs, function() {
				if (!fs.existsSync(parent.buildPath)) {
					fs.mkdirSync(parent.buildPath);
					fs.mkdirSync(parent.buildPath + 'lng');
					ncp(__dirname + "/test/", parent.buildPath + "test", function(err) {
						if (err) {
							console.error(err);
							reject(err);
						}
					});
					ncp(__dirname + "/assets/", parent.buildPath + "assets", function(err) {
						if (err) {
							console.error(err);
							reject(err);
						}
						ncp(__dirname + "/webform.js", parent.buildPath + parent.baseFileName + "webform.js", function(err) {
							if (err) {
								console.error(err);
								reject(err);
							}
							ncp(__dirname + "/webform.css", parent. buildPath + parent.baseFileName + "webform.css", function(err) {
								if (err) {
									console.error(err);
									reject(err);
								}
								ncp(__dirname + "/ct-codelists-en.json", parent. buildPath  + "ct-codelists-en.json", function(err) {
									if (err) {
										console.error(err);
										reject(err);
									}
									resolve();
								});
							});
						});
					});
				}
			});
		});
	}

	/**
	 * setHeaer
	 * @param pageTitle
	 */

	getHeader() {

		return `
<!DOCTYPE html>

<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>{{'pagetitle' | translate}}</title>

<link rel="stylesheet" type="text/css" href="./assets/css/webform.all.min.css"/>
<link rel="stylesheet" type="text/css" href="./${this.baseFileName}webform.css"/>

<link rel="shortcut icon" type="image/x-icon" href="./assets/img/favicon.ico"/>

<eea-header></eea-header>

EEA

Web Form

	<eea-language data="codeLists" lang="{{selectedLanguage}}" chooselanguage="{{'chooselanguage' | translate}}"></eea-language>

#

{{'formtitle' | translate}}

	`;
	}

	/**
	 * getFooter
	 */
	getFooter() {

		return `

<eea-toolbar scp="this" off="{{'off' | translate}}" on="{{'on' | translate}}" save="{{'save' | translate}}" printpreview="{{'printpreview' | translate}}" close="{{'close' | translate}}" validation="{{'validation' | translate}}"></eea-toolbar>

		<eea-form-build date="${new Date()}"></eea-form-build>

`;
	}

	/**
	 * getFile
	 * @param file
	 */
	getFile(file) {
		return new Promise((resolve, reject) => {
			fs.readFile(file, 'utf8', (err, data) => {
				if (err) {
					XSDWebFormParserError.reportError(`No such file or directory ${err.path}\n`);
					reject(err);
				}
				resolve(data);
			});
		});
	}

	/**
	 * createFile - Save output file
	 * @param filename
	 * @param content
	 */
	createFile(filename, content, log = true) {
		var parent = this;
		return new Promise((resolve, reject) => {
			fs.writeFile(filename, content, function(err) {
				if (err) {
					console.log(err);
					reject(err);
				} else {
					if (parent.showLog && log)
						console.log(`\x1b[2m\x1b[36mThe file ${filename} was saved\x1b[0m\n`);
					resolve();
				}
			});
		});
	}
}

var xsdWebForm = new XSDWebForm(process.argv);

module.exports.xsdWebForm = xsdWebForm;

// heapdump.writeSnapshot('./' + Date.now() + '.heapsnapshot');<|endoftext|>


<commit_before>
/*
 * A general superclass for nodes on the Pedigree graph. Contains connections
 * and basic information about gender, ID and a graphics element.
 *
 * @param x the x coordinate on the canvas
 * @param x the y coordinate on the canvas
 * @param gender should be "U", "F", or "M" depending on the gender
 * @param id the unique ID number of this node
 */

var AbstractPerson = Class.create(AbstractNode, {

    initialize: function($super, x, y, gender, id) {
        this._parentPartnership = null;
        this._partnerships = [];
        this._gender = this.parseGender(gender);
        $super(x, y, id);
      },

    /*
     * Initializes the object responsible for creating graphics for this node
     *
     * @param x the x coordinate on the canvas at which the node is centered
     * @param y the y coordinate on the canvas at which the node is centered
     */
    generateGraphics: function(x, y) {
        return new AbstractPersonVisuals(this, x, y);
    },

    /*
     * Reads a string of input and converts it into the standard gender format of "M","F" or "U".
     * Defaults to "U" if string is not recognized
     *
     * @param gender the string to be parsed
     */
    parseGender: function(gender) {
        return (gender == 'M' || gender == 'F')?gender:'U';
    },

    /*
     * Returns "U", "F" or "M" depending on the gender of this node
     */
    getGender: function() {
        return this._gender;
    },

    /*
     * Updates the gender of this node and (optionally) updates the
     * graphics. Updates gender of all partners if it is unknown.
     * Returns an array of nodes visited during the partner traversal.
     *
     * @param gender should be "U", "F", or "M" depending on the gender
     * @param forceDraw set to true if you want to update the graphics
     * @param visitedNodes an array of nodes that were visited during the traversal up until
     *  this node. OMIT this parameter. It is used for internal functionality.
     */
    setGender: function(gender, forceDraw, visitedNodes) {
        var visited = (visitedNodes) ? visitedNodes : [];
        visited.push(this);
        if(this.getPartners().length == 0) {
            this._gender = this.parseGender(gender);
            this.getGraphics().setGenderSymbol();
        }
        else if(this.getGender() == "U") {
            var me = this;
            this._gender = this.parseGender(gender);
            this.getGraphics().setGenderSymbol();
            this.getPartners().each(function(partner) {
                if(visited.indexOf(partner) == -1) {
                    visited = partner.setGender(me.getOppositeGender(), forceDraw, visited);
                }
            });
        }
        return visited;
    },

    /*
     * Returns an array of Partnership objects of this node
     */
    getPartnerships: function() {
        return this._partnerships;
    },

    /*
     * Returns the Partnership affiliated with partner
     *
     * @partner can be a Person or a PlaceHolder
     */
    getPartnership: function(partner) {
        var partnerships = this.getPartnerships();
        for(var i = 0; i < partnerships.length; i++) {
            if(partnerships[i].getPartnerOf(this) == partner) {
                return partnerships[i];
            }
        }
        return null;
    },

    /*
     * Returns an array nodes that share a Partnership with this node
     */
    getPartners: function() {
        var partners = [];
        var me = this;
        this.getPartnerships().each(function(partnership) {
            var partner = partnership.getPartnerOf(me);
            partner && partners.push(partner);
        });
        return partners;
    },

    /*
     * Adds a new partnership to the list of partnerships of this node
     *
     * @param partnership is a Partnership object with this node as one of the partners
     */
    addPartnership: function(partnership) {
       if(this.getPartners().indexOf(partnership.getPartnerOf(this)) == -1) {
           this._partnerships.push(partnership);
       }
    },

    /*
     * Removes a partnership from the list of partnerships
     *
     * @param partnership is a Partnership object with this node as one of the partners
     */
    removePartnership: function(partnership) {
        this._partnerships = this._partnerships.without(partnership);
    },

    /*
     * Returns a Partnership containing this the parent nodes
     */
    getParentPartnership: function() {
        return this._parentPartnership;
    },

    /*
     * Replaces the parents Partnership with the one passed in the parameter
     *
     * @param partnership is a Partnership object that should have this node listed as a child
     */
    setParentPartnership: function(partnership) {
        this._parentPartnership = partnership;
    },

    /*
     * Returns an array containing the two parent AbstractPerson nodes of this node.
     */
    getParents: function() {
        if(this.getParentPartnership()){
            return [this.getParentPartnership().getPartners()[0], this.getParentPartnership().getPartners()[1]]
        }
        return null;
    },

    /*
     * Creates a Partnership of two new Person nodes of opposite gender, and sets parents to this partnership
     */
    createParents: function() {
        if(this.getParentPartnership() == null) {
            var positions = editor.findPosition ({above : this.getID()}, ['mom', 'dad']);
            var mother = editor.addNode(positions['mom'].x, positions['mom'].y, "F", false),
                father = editor.addNode(positions['dad'].x, positions['dad'].y, "M", false);
            var partnership = editor.addPartnership(mother, father);
            this.addParents(partnership);
        }
    },

    /*
     * Sets parents to the partnership passed in the parameter, and adds this node to partnership's list of children
     */
    addParents: function(partnership) {
        if(this.getParentPartnership() == null) {
            partnership.addChild(this);
        }
    },

    removeParents: function() {
        if(this.getParentPartnership()) {
            this.getParentPartnership().remove();
        }
    },

    addParent: function(parent) {
        if(parent.canBeParentOf(this)) {
            var partnership = parent.createPartner(true, true);
            partnership.addChild(this);
        }
    },

    /*
     * Returns a string representing the opposite gender of this node ("M" or "F"). Returns "U"
     * if the gender of this node is unknown
     */
    getOppositeGender : function() {
        if (this.getGender() == "U") {
            return "U";
        }
        else if(this.getGender() == "M") {
            return "F";
        }
        else {
            return "M";
        }
    },
    /*
     * Creates a new node and generates a Partnership with this node.
     * Returns the Partnership.
     *
     * @param isPlaceHolder set to true if the new partner should be a PlaceHolder
     */
    createPartner: function(isPlaceHolder, noChild) {
        var position = editor.findPosition({side: this.getID()}),
            partner = editor.addNode(position.x, position.y, this.getOppositeGender(), isPlaceHolder);
        return this.addPartner(partner, noChild);
    },

    /*
     * Creates a new Partnership with the partner passed in the parameter.
     * Does not duplicate a partnership if one already exists.
     * Returns the new Partnership or the preexisting partnership
     *
     * @param partner a Person or PlaceHolder.
     */
    addPartner: function(partner, noChild) {
        if(this.getPartners().indexOf(partner) != -1){
            return this.getPartnership(partner);
        }
        else if(this.canPartnerWith(partner)) {
            var partnership = editor.addPartnership(this, partner);

            if(this.getGender() == 'U' && partner.getGender() != 'U') {
                this.setGender(partner.getOppositeGender(), true, null);
            }
            else if(this.getGender() != 'U' && partner.getGender() == 'U') {
                partner.setGender(this.getOppositeGender(), true, null);
            }

            if(partnership.getChildren().length == 0 && !noChild) {
                partnership.createChild(true);
            }

            return partnership;
        }
    },

    /*
     * Returns an array of nodes that are children from all of this node's Partnerships.
     * The array can include PlaceHolders.
     */
    getChildren: function() {
        var children = [];
        this.getPartnerships().each(function(partnership) {
            children = children.concat(partnership.getChildren())
        });
        return children;
    },

    createChild: function(isPlaceHolder) {
      var partnership = this.createPartner(true, true);
      partnership.createChild(isPlaceHolder);
    },

    addChild: function(child) {
        if(this.canBeParentOf(child)) {
            var partnership = this.createPartner(true, true);
            partnership.addChild(child);
            return child;
        }
        return null;
    },

    /*
     * Returns true if this node is a parent of otherNode
     *
     * @param otherNode can be a Person or a PlaceHolder
     */
    isParentOf: function(otherNode) {
        return (this.getChildren().indexOf(otherNode) > -1);
    },

    /*
     * Returns true if this node is a descendant of otherNode
     *
     * @param otherNode can be a Person or a PlaceHolder
     */
    isDescendantOf: function(otherNode) {
        if(otherNode.isParentOf(this)) {
            return true;
        }
        else {
            var found = false,
                children = otherNode.getChildren(),
                i = 0;
            while((i < children.length) && !found) {
                found = this.isDescendantOf(children[i]);
                i++;
            }
            return found;
        }
    },

    /*
     * Returns true if this node is an ancestor of otherNode
     *
     * @param otherNode can be a Person or a PlaceHolder
     */
    isAncestorOf: function(otherNode) {
        return otherNode.isDescendantOf(this);
    },

    /*
     * Returns true if this node is a partner of otherNode
     *
     * @param otherNode can be a Person or a PlaceHolder
     */
    isPartnerOf: function(otherNode) {
        return this.getPartners().indexOf(otherNode) > -1;
    },

    /*
     * Returns true if this node is related to otherNode
     *
     * @param otherNode can be a Person or a PlaceHolder
     */
    isRelatedTo: function(otherNode) {
        var getPersonNeighbors = function(node) {
            var neighbors = [];
            node.getParentPartnership() && neighbors.push(node.getParentPartnership().getPartner1(), node.getParentPartnership().getPartner2());
            neighbors = neighbors.concat(node.getChildren());
            neighbors = neighbors.concat(node.getPartners());
            return neighbors;
        };

        var front = [this],
            next = [],
            visited = {};
        visited[this.getID()] = true;
        while(front.length > 0) {
            for(var i = 0; i<front.length; i++) {
                var neighbors = getPersonNeighbors(front[i]);
                for(var k = 0; i<neighbors.length; k++) {
                    if(neighbors[k] == otherNode) {
                        return true;
                    }
                    else if(!visited[neighbors[k].getID()]) {
                        next.push(neighbors[k]);
                        visited[neighbors[k].getID()] = true;
                    }
                }
            }
            front.clear();
            front = next;
            next.clear();
        }
        return false;
    },

    /*
     * Returns true if this node can have a heterosexual Partnership with otherNode
     *
     * @param otherNode is a Person
     */
    canPartnerWith: function(otherNode) {
        var oppositeGender = (this.getOppositeGender() == otherNode.getGender() || this.getGender() == "U" || otherNode.getGender() == "U");
        var numSteps = this.getStepsToNode(otherNode)[0];
        var oddStepsAway = (numSteps == null || numSteps%2 == 1);
        return oppositeGender && oddStepsAway;
    },

    /*
     * Returns true if this node can be a parent of otherNode
     *
     * @param otherNode is a Person
     */
    canBeParentOf: function(otherNode) {
        var isDescendant = this.isDescendantOf(otherNode);
        return otherNode.getParentPartnership() == null && this.getChildren().indexOf(otherNode) == -1 && !isDescendant;
    },

    /*
     * Breaks connections with all related nodes and removes this node from
     * the record.
     * (Optional) Removes all descendant nodes and their relatives that will become unrelated to the proband as a result
     *
     * @param isRecursive set to true if you want to remove all unrelated descendants as well
     */
    remove: function($super, isRecursive) {
        if(isRecursive) {
            $super(true)
        }
        else {
            var me = this,
                toRemove = [],
                parents = this.getParentPartnership();
            parents && parents.removeChild(me);
            this.getPartnerships().each(function(partnership) {
                partnership.remove(false);
            });
            editor.removeNode(this);
            this.getGraphics().remove();
        }
    },

    /*
     * Returns the parent's Partnership
     */
    getUpperNeighbors: function() {
        return this.getParentPartnership() ? [this.getParentPartnership()] : [];
    },

    /*
     * Returns all of this node's Partnerships
     */
    getSideNeighbors: function() {
        return this.getPartnerships();
    },

    /*
     * Returns an array with the number of partnerships between this node and otherNode, and the nodes visited
     * in the process of the traversal
     *
     * @param otherNode an AbstractNode whose distance (in partnerships) from this node you're trying to calculate
     * @param visitedNodes an array of nodes that were visited in the result of the traversal. This parameter is used
     * internally so omit it when calling the function
     */
    getStepsToNode: function(otherNode, visitedNodes) {
        var visited = (visitedNodes) ? visitedNodes : [];
        visited.push(this);
        if(this === otherNode) {
            return [0, visited];
        }
        else {
            var numSteps = null;
            this.getPartners().each(function(partner) {
                if(visited.indexOf(partner) == -1) {
                    numSteps = partner.getStepsToNode(otherNode, visited)[0];
                    if(numSteps != null) {
                        numSteps = 1 + numSteps;
                        throw $break;
                    }
                }
            });
            return [numSteps, visited];
        }
    }
});<commit_msg>This method was moved to AbstractNode in 850c9b07491fe8fbf735e5f3a598a9500a0b8c09<commit_after>
/*
 * A general superclass for nodes on the Pedigree graph. Contains connections
 * and basic information about gender, ID and a graphics element.
 *
 * @param x the x coordinate on the canvas
 * @param x the y coordinate on the canvas
 * @param gender should be "U", "F", or "M" depending on the gender
 * @param id the unique ID number of this node
 */

var AbstractPerson = Class.create(AbstractNode, {

    initialize: function($super, x, y, gender, id) {
        this._parentPartnership = null;
        this._partnerships = [];
        this._gender = this.parseGender(gender);
        $super(x, y, id);
      },

    /*
     * Initializes the object responsible for creating graphics for this node
     *
     * @param x the x coordinate on the canvas at which the node is centered
     * @param y the y coordinate on the canvas at which the node is centered
     */
    generateGraphics: function(x, y) {
        return new AbstractPersonVisuals(this, x, y);
    },

    /*
     * Reads a string of input and converts it into the standard gender format of "M","F" or "U".
     * Defaults to "U" if string is not recognized
     *
     * @param gender the string to be parsed
     */
    parseGender: function(gender) {
        return (gender == 'M' || gender == 'F')?gender:'U';
    },

    /*
     * Returns "U", "F" or "M" depending on the gender of this node
     */
    getGender: function() {
        return this._gender;
    },

    /*
     * Updates the gender of this node and (optionally) updates the
     * graphics. Updates gender of all partners if it is unknown.
     * Returns an array of nodes visited during the partner traversal.
     *
     * @param gender should be "U", "F", or "M" depending on the gender
     * @param forceDraw set to true if you want to update the graphics
     * @param visitedNodes an array of nodes that were visited during the traversal up until
     *  this node. OMIT this parameter. It is used for internal functionality.
     */
    setGender: function(gender, forceDraw, visitedNodes) {
        var visited = (visitedNodes) ? visitedNodes : [];
        visited.push(this);
        if(this.getPartners().length == 0) {
            this._gender = this.parseGender(gender);
            this.getGraphics().setGenderSymbol();
        }
        else if(this.getGender() == "U") {
            var me = this;
            this._gender = this.parseGender(gender);
            this.getGraphics().setGenderSymbol();
            this.getPartners().each(function(partner) {
                if(visited.indexOf(partner) == -1) {
                    visited = partner.setGender(me.getOppositeGender(), forceDraw, visited);
                }
            });
        }
        return visited;
    },

    /*
     * Returns an array of Partnership objects of this node
     */
    getPartnerships: function() {
        return this._partnerships;
    },

    /*
     * Returns the Partnership affiliated with partner
     *
     * @partner can be a Person or a PlaceHolder
     */
    getPartnership: function(partner) {
        var partnerships = this.getPartnerships();
        for(var i = 0; i < partnerships.length; i++) {
            if(partnerships[i].getPartnerOf(this) == partner) {
                return partnerships[i];
            }
        }
        return null;
    },

    /*
     * Returns an array nodes that share a Partnership with this node
     */
    getPartners: function() {
        var partners = [];
        var me = this;
        this.getPartnerships().each(function(partnership) {
            var partner = partnership.getPartnerOf(me);
            partner && partners.push(partner);
        });
        return partners;
    },

    /*
     * Adds a new partnership to the list of partnerships of this node
     *
     * @param partnership is a Partnership object with this node as one of the partners
     */
    addPartnership: function(partnership) {
       if(this.getPartners().indexOf(partnership.getPartnerOf(this)) == -1) {
           this._partnerships.push(partnership);
       }
    },

    /*
     * Removes a partnership from the list of partnerships
     *
     * @param partnership is a Partnership object with this node as one of the partners
     */
    removePartnership: function(partnership) {
        this._partnerships = this._partnerships.without(partnership);
    },

    /*
     * Returns a Partnership containing this the parent nodes
     */
    getParentPartnership: function() {
        return this._parentPartnership;
    },

    /*
     * Replaces the parents Partnership with the one passed in the parameter
     *
     * @param partnership is a Partnership object that should have this node listed as a child
     */
    setParentPartnership: function(partnership) {
        this._parentPartnership = partnership;
    },

    /*
     * Returns an array containing the two parent AbstractPerson nodes of this node.
     */
    getParents: function() {
        if(this.getParentPartnership()){
            return [this.getParentPartnership().getPartners()[0], this.getParentPartnership().getPartners()[1]]
        }
        return null;
    },

    /*
     * Creates a Partnership of two new Person nodes of opposite gender, and sets parents to this partnership
     */
    createParents: function() {
        if(this.getParentPartnership() == null) {
            var positions = editor.findPosition ({above : this.getID()}, ['mom', 'dad']);
            var mother = editor.addNode(positions['mom'].x, positions['mom'].y, "F", false),
                father = editor.addNode(positions['dad'].x, positions['dad'].y, "M", false);
            var partnership = editor.addPartnership(mother, father);
            this.addParents(partnership);
        }
    },

    /*
     * Sets parents to the partnership passed in the parameter, and adds this node to partnership's list of children
     */
    addParents: function(partnership) {
        if(this.getParentPartnership() == null) {
            partnership.addChild(this);
        }
    },

    removeParents: function() {
        if(this.getParentPartnership()) {
            this.getParentPartnership().remove();
        }
    },

    addParent: function(parent) {
        if(parent.canBeParentOf(this)) {
            var partnership = parent.createPartner(true, true);
            partnership.addChild(this);
        }
    },

    /*
     * Returns a string representing the opposite gender of this node ("M" or "F"). Returns "U"
     * if the gender of this node is unknown
     */
    getOppositeGender : function() {
        if (this.getGender() == "U") {
            return "U";
        }
        else if(this.getGender() == "M") {
            return "F";
        }
        else {
            return "M";
        }
    },
    /*
     * Creates a new node and generates a Partnership with this node.
     * Returns the Partnership.
     *
     * @param isPlaceHolder set to true if the new partner should be a PlaceHolder
     */
    createPartner: function(isPlaceHolder, noChild) {
        var position = editor.findPosition({side: this.getID()}),
            partner = editor.addNode(position.x, position.y, this.getOppositeGender(), isPlaceHolder);
        return this.addPartner(partner, noChild);
    },

    /*
     * Creates a new Partnership with the partner passed in the parameter.
     * Does not duplicate a partnership if one already exists.
     * Returns the new Partnership or the preexisting partnership
     *
     * @param partner a Person or PlaceHolder.
     */
    addPartner: function(partner, noChild) {
        if(this.getPartners().indexOf(partner) != -1){
            return this.getPartnership(partner);
        }
        else if(this.canPartnerWith(partner)) {
            var partnership = editor.addPartnership(this, partner);

            if(this.getGender() == 'U' && partner.getGender() != 'U') {
                this.setGender(partner.getOppositeGender(), true, null);
            }
            else if(this.getGender() != 'U' && partner.getGender() == 'U') {
                partner.setGender(this.getOppositeGender(), true, null);
            }

            if(partnership.getChildren().length == 0 && !noChild) {
                partnership.createChild(true);
            }

            return partnership;
        }
    },

    /*
     * Returns an array of nodes that are children from all of this node's Partnerships.
     * The array can include PlaceHolders.
     */
    getChildren: function() {
        var children = [];
        this.getPartnerships().each(function(partnership) {
            children = children.concat(partnership.getChildren())
        });
        return children;
    },

    createChild: function(isPlaceHolder) {
      var partnership = this.createPartner(true, true);
      partnership.createChild(isPlaceHolder);
    },

    addChild: function(child) {
        if(this.canBeParentOf(child)) {
            var partnership = this.createPartner(true, true);
            partnership.addChild(child);
            return child;
        }
        return null;
    },

    /*
     * Returns true if this node is a parent of otherNode
     *
     * @param otherNode can be a Person or a PlaceHolder
     */
    isParentOf: function(otherNode) {
        return (this.getChildren().indexOf(otherNode) > -1);
    },

    /*
     * Returns true if this node is a descendant of otherNode
     *
     * @param otherNode can be a Person or a PlaceHolder
     */
    isDescendantOf: function(otherNode) {
        if(otherNode.isParentOf(this)) {
            return true;
        }
        else {
            var found = false,
                children = otherNode.getChildren(),
                i = 0;
            while((i < children.length) && !found) {
                found = this.isDescendantOf(children[i]);
                i++;
            }
            return found;
        }
    },

    /*
     * Returns true if this node is an ancestor of otherNode
     *
     * @param otherNode can be a Person or a PlaceHolder
     */
    isAncestorOf: function(otherNode) {
        return otherNode.isDescendantOf(this);
    },

    /*
     * Returns true if this node is a partner of otherNode
     *
     * @param otherNode can be a Person or a PlaceHolder
     */
    isPartnerOf: function(otherNode) {
        return this.getPartners().indexOf(otherNode) > -1;
    },

    /*
     * Returns true if this node can have a heterosexual Partnership with otherNode
     *
     * @param otherNode is a Person
     */
    canPartnerWith: function(otherNode) {
        var oppositeGender = (this.getOppositeGender() == otherNode.getGender() || this.getGender() == "U" || otherNode.getGender() == "U");
        var numSteps = this.getStepsToNode(otherNode)[0];
        var oddStepsAway = (numSteps == null || numSteps%2 == 1);
        return oppositeGender && oddStepsAway;
    },

    /*
     * Returns true if this node can be a parent of otherNode
     *
     * @param otherNode is a Person
     */
    canBeParentOf: function(otherNode) {
        var isDescendant = this.isDescendantOf(otherNode);
        return otherNode.getParentPartnership() == null && this.getChildren().indexOf(otherNode) == -1 && !isDescendant;
    },

    /*
     * Breaks connections with all related nodes and removes this node from
     * the record.
     * (Optional) Removes all descendant nodes and their relatives that will become unrelated to the proband as a result
     *
     * @param isRecursive set to true if you want to remove all unrelated descendants as well
     */
    remove: function($super, isRecursive) {
        if(isRecursive) {
            $super(true)
        }
        else {
            var me = this,
                toRemove = [],
                parents = this.getParentPartnership();
            parents && parents.removeChild(me);
            this.getPartnerships().each(function(partnership) {
                partnership.remove(false);
            });
            editor.removeNode(this);
            this.getGraphics().remove();
        }
    },

    /*
     * Returns the parent's Partnership
     */
    getUpperNeighbors: function() {
        return this.getParentPartnership() ? [this.getParentPartnership()] : [];
    },

    /*
     * Returns all of this node's Partnerships
     */
    getSideNeighbors: function() {
        return this.getPartnerships();
    },

    /*
     * Returns an array with the number of partnerships between this node and otherNode, and the nodes visited
     * in the process of the traversal
     *
     * @param otherNode an AbstractNode whose distance (in partnerships) from this node you're trying to calculate
     * @param visitedNodes an array of nodes that were visited in the result of the traversal. This parameter is used
     * internally so omit it when calling the function
     */
    getStepsToNode: function(otherNode, visitedNodes) {
        var visited = (visitedNodes) ? visitedNodes : [];
        visited.push(this);
        if(this === otherNode) {
            return [0, visited];
        }
        else {
            var numSteps = null;
            this.getPartners().each(function(partner) {
                if(visited.indexOf(partner) == -1) {
                    numSteps = partner.getStepsToNode(otherNode, visited)[0];
                    if(numSteps != null) {
                        numSteps = 1 + numSteps;
                        throw $break;
                    }
                }
            });
            return [numSteps, visited];
        }
    }
});<|endoftext|>


<commit_before>const azStorage = require('azure-storage');
const entGen = azStorage.TableUtilities.entityGenerator;
const promise = require('promise');

var AccountManager = module.exports = function (config) {
	this.config = config;
    _this.tableService = azStorage.createTableService(config.storage.storageAccount, _this.config.storage.accessKey);
}

AccountManager.prototype = {
    config: null,
    tableService: null,

    init: function init() {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.tableService.createTableIfNotExists(config.tableName, function(error, result, response) {
                if (error) {
                    console.error('Error initializing account manager table service', _this.tableService, error);
                    return reject(error);
                }

                // result contains true if created; false if already exists
                if (result) {
                    console.info('Azure table created with name', _this.config.tableName);
                } else {
                    console.info('Azure table already existed with name', _this.config.tableName);
                }

                resolve(result);
            });
        });
    },

    createAccount: function createAccount(clientInfo) {
        var _this = this;
        var accountEntity = clientInfoToDBFormat(clientInfo);

        return new promise(function (resolve, reject) {
            _this.tableService.insertEntity(_this.config.tableName, accountEntity, function(error, result, response) {
                if (error) {
                    console.error('Error adding account entity', accountEntity, error);
                    return reject(error);
                }

                // result contains the ETag for the new entity
                resolve(result);
            });
        });
    },

    loadOrCreateAccount: function loadOrCreateAccount(clientInfoDefaults) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.loadAccount(clientInfoDefaults.id)
                .then(resolve)
                .catch(function(err) {
                    _this.createAccount(clientInfoDefaults)
                        .then(function (etag) {
                            return clientInfoDefaults;
                        })
                        .catch(reject);
                });
        });
    },

    loadAccount: function loadAccount(id) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.tableService.retrieveEntity(_this.config.tableName, 'users', id, function(error, result, response) {
                if (error) {
                    console.error('Error retrieving entity', id, error);
                    return reject(error);
                }

                // result contains the entity
                resolve(dbFormatToClientInfo(result));
            });
        });
    },

    updateAccount: function updateAccount(clientInfo) {
        var _this = this;
        var accountEntity = clientInfoToDBFormat(clientInfo);

        return new promise(function (resolve, reject) {
            _this.tableService.replaceEntity(_this.config.tableName, accountEntity, function(error, result, response) {
                if (error) {
                    console.error('Error updating account entity', accountEntity, error);
                    return reject(error);
                }

                // result contains the ETag for the new entity
                resolve(result);
            });
        });
    },

    setAccountProperties: function setAccountProperty(id, properties) {
        var _this = this;

        properties.id = id;
        var accountEntity = clientInfoToDBFormat(properties);

        return new promise(function (resolve, reject) {
            _this.tableService.mergeEntity(_this.config.tableName, accountEntity, function(error, result, response) {
                if (error) {
                    console.error('Error merging account entity', accountEntity, error);
                    return reject(error);
                }

                // result contains the ETag for the new entity
                return _this.loadAccount(id)
                    .then(resolve)
                    .catch(reject);
            });
        });
    },
};

function clientInfoToDBFormat(clientInfo) {
    var data = {
        PartitionKey: entGen.String('users'),
        RowKey: entGen.String(clientInfo.id),
    };

    for (var i in clientInfo) {
        if (i === 'id') {
            continue;
        }

        if (typeof clientInfo[i] === 'string') {
            data[i] = entGen.String(clientInfo[i]);
        } else if (typeof clientInfo[i] === 'number') {
            data[i] = entGen.Int32(clientInfo[i]);
        } else if (typeof clientInfo[i] instanceof Date) {
            data[i] = entGen.DateTime(clientInfo[i]);
        }
    }

    return data;
}

function dbFormatToClientInfo(dbFormat) {
    var clientInfo = {};

    for (var i in dbFormat) {
        if (i === 'RowKey') {
            clientInfo['id'] = dbFormat[i];
        } else if (i === 'PartitionKey') {
            continue;
        } else {
            clientInfo[i] = dbFormat[i];
        }
    }

    return clientInfo;
}<commit_msg>Update accountManager constructor<commit_after>const azStorage = require('azure-storage');
const entGen = azStorage.TableUtilities.entityGenerator;
const promise = require('promise');

var AccountManager = module.exports = function (config) {
	this.config = config;
    this.tableService = azStorage.createTableService(this.config.storage.storageAccount, this.config.storage.accessKey);
}

AccountManager.prototype = {
    config: null,
    tableService: null,

    init: function init() {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.tableService.createTableIfNotExists(config.tableName, function(error, result, response) {
                if (error) {
                    console.error('Error initializing account manager table service', _this.tableService, error);
                    return reject(error);
                }

                // result contains true if created; false if already exists
                if (result) {
                    console.info('Azure table created with name', _this.config.tableName);
                } else {
                    console.info('Azure table already existed with name', _this.config.tableName);
                }

                resolve(result);
            });
        });
    },

    createAccount: function createAccount(clientInfo) {
        var _this = this;
        var accountEntity = clientInfoToDBFormat(clientInfo);

        return new promise(function (resolve, reject) {
            _this.tableService.insertEntity(_this.config.tableName, accountEntity, function(error, result, response) {
                if (error) {
                    console.error('Error adding account entity', accountEntity, error);
                    return reject(error);
                }

                // result contains the ETag for the new entity
                resolve(result);
            });
        });
    },

    loadOrCreateAccount: function loadOrCreateAccount(clientInfoDefaults) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.loadAccount(clientInfoDefaults.id)
                .then(resolve)
                .catch(function(err) {
                    _this.createAccount(clientInfoDefaults)
                        .then(function (etag) {
                            return clientInfoDefaults;
                        })
                        .catch(reject);
                });
        });
    },

    loadAccount: function loadAccount(id) {
        var _this = this;
        return new promise(function (resolve, reject) {
            _this.tableService.retrieveEntity(_this.config.tableName, 'users', id, function(error, result, response) {
                if (error) {
                    console.error('Error retrieving entity', id, error);
                    return reject(error);
                }

                // result contains the entity
                resolve(dbFormatToClientInfo(result));
            });
        });
    },

    updateAccount: function updateAccount(clientInfo) {
        var _this = this;
        var accountEntity = clientInfoToDBFormat(clientInfo);

        return new promise(function (resolve, reject) {
            _this.tableService.replaceEntity(_this.config.tableName, accountEntity, function(error, result, response) {
                if (error) {
                    console.error('Error updating account entity', accountEntity, error);
                    return reject(error);
                }

                // result contains the ETag for the new entity
                resolve(result);
            });
        });
    },

    setAccountProperties: function setAccountProperty(id, properties) {
        var _this = this;

        properties.id = id;
        var accountEntity = clientInfoToDBFormat(properties);

        return new promise(function (resolve, reject) {
            _this.tableService.mergeEntity(_this.config.tableName, accountEntity, function(error, result, response) {
                if (error) {
                    console.error('Error merging account entity', accountEntity, error);
                    return reject(error);
                }

                // result contains the ETag for the new entity
                return _this.loadAccount(id)
                    .then(resolve)
                    .catch(reject);
            });
        });
    },
};

function clientInfoToDBFormat(clientInfo) {
    var data = {
        PartitionKey: entGen.String('users'),
        RowKey: entGen.String(clientInfo.id),
    };

    for (var i in clientInfo) {
        if (i === 'id') {
            continue;
        }

        if (typeof clientInfo[i] === 'string') {
            data[i] = entGen.String(clientInfo[i]);
        } else if (typeof clientInfo[i] === 'number') {
            data[i] = entGen.Int32(clientInfo[i]);
        } else if (typeof clientInfo[i] instanceof Date) {
            data[i] = entGen.DateTime(clientInfo[i]);
        }
    }

    return data;
}

function dbFormatToClientInfo(dbFormat) {
    var clientInfo = {};

    for (var i in dbFormat) {
        if (i === 'RowKey') {
            clientInfo['id'] = dbFormat[i];
        } else if (i === 'PartitionKey') {
            continue;
        } else {
            clientInfo[i] = dbFormat[i];
        }
    }

    return clientInfo;
}<|endoftext|>


<commit_before>import { formatIsoTimeZoneOffset } from '../datelib/utils'

describe('events as a json feed', function() {

  pushOptions({
    defaultDate: '2014-05-01',
    defaultView: 'dayGridMonth'
  })

  beforeEach(function() {
    XHRMock.setup()
  })

  afterEach(function() {
    XHRMock.teardown()
  })

  it('requests correctly when local timezone', function(done) {
    const START = '2014-04-27T00:00:00'
    const END = '2014-06-08T00:00:00'

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        start: START + formatIsoTimeZoneOffset(new Date(START)),
        end: END + formatIsoTimeZoneOffset(new Date(END))
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: 'my-feed.php',
      timeZone: 'local'
    })
  })

  it('requests correctly when UTC timezone', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        start: '2014-04-27T00:00:00Z',
        end: '2014-06-08T00:00:00Z',
        timeZone: 'UTC'
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: 'my-feed.php',
      timeZone: 'UTC'
    })
  })

  it('requests correctly when named timezone', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        start: '2014-04-27T00:00:00',
        end: '2014-06-08T00:00:00',
        timeZone: 'America/Chicago'
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: 'my-feed.php',
      timeZone: 'America/Chicago'
    })
  })

  it('requests correctly with event source extended form', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        start: '2014-04-27T00:00:00',
        end: '2014-06-08T00:00:00',
        timeZone: 'America/Chicago'
      })
      return res.status(200).header('content-type', 'application/json').body(
        JSON.stringify([
          {
            title: 'my event',
            start: '2014-05-21'
          }
        ])
      )
    })

    initCalendar({
      eventSources: [ {
        url: 'my-feed.php',
        className: 'customeventclass'
      } ],
      timeZone: 'America/Chicago',
      eventRender: function(arg) {
        expect(arg.el).toHaveClass('customeventclass')
        done()
      }
    })
  })

  it('accepts a data object', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        timeZone: 'UTC',
        start: '2014-04-27T00:00:00Z',
        end: '2014-06-08T00:00:00Z',
        customParam: 'yes'
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      eventSources: [ {
        url: 'my-feed.php',
        data: {
          customParam: 'yes'
        }
      } ]
    })
  })

  it('accepts a extraParams object', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        timeZone: 'UTC',
        start: '2014-04-27T00:00:00Z',
        end: '2014-06-08T00:00:00Z',
        customParam: 'yes'
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      eventSources: [ {
        url: 'my-feed.php',
        extraParams: {
          customParam: 'yes'
        }
      } ]
    })
  })

  it('accepts a dynamic extraParams function', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        timeZone: 'UTC',
        start: '2014-04-27T00:00:00Z',
        end: '2014-06-08T00:00:00Z',
        customParam: 'heckyeah'
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      eventSources: [ {
        url: 'my-feed.php',
        extraParams: function() {
          return {
            customParam: 'heckyeah'
          }
        }
      } ]
    })
  })

  it('calls loading callback', function(done) {
    var loadingCallArgs = []

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: { url: 'my-feed.php' },
      loading: function(bool) {
        loadingCallArgs.push(bool)
      },
      _eventsPositioned: function() {
        expect(loadingCallArgs).toEqual([ true, false ])
        done()
      }
    })
  })

  it('has and Event Source object with certain props', function() {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: { url: 'my-feed.php' }
    })
    expect(currentCalendar.getEventSources()[0].url).toBe('my-feed.php')
  })

})
<commit_msg>test that POST for json feeds work<commit_after>import { formatIsoTimeZoneOffset } from '../datelib/utils'

describe('events as a json feed', function() {

  pushOptions({
    defaultDate: '2014-05-01',
    defaultView: 'dayGridMonth'
  })

  beforeEach(function() {
    XHRMock.setup()
  })

  afterEach(function() {
    XHRMock.teardown()
  })

  it('requests correctly when local timezone', function(done) {
    const START = '2014-04-27T00:00:00'
    const END = '2014-06-08T00:00:00'

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        start: START + formatIsoTimeZoneOffset(new Date(START)),
        end: END + formatIsoTimeZoneOffset(new Date(END))
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: 'my-feed.php',
      timeZone: 'local'
    })
  })

  it('requests correctly when UTC timezone', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        start: '2014-04-27T00:00:00Z',
        end: '2014-06-08T00:00:00Z',
        timeZone: 'UTC'
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: 'my-feed.php',
      timeZone: 'UTC'
    })
  })

  it('requests correctly when named timezone', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        start: '2014-04-27T00:00:00',
        end: '2014-06-08T00:00:00',
        timeZone: 'America/Chicago'
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: 'my-feed.php',
      timeZone: 'America/Chicago'
    })
  })

  it('requests correctly with event source extended form', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        start: '2014-04-27T00:00:00',
        end: '2014-06-08T00:00:00',
        timeZone: 'America/Chicago'
      })
      return res.status(200).header('content-type', 'application/json').body(
        JSON.stringify([
          {
            title: 'my event',
            start: '2014-05-21'
          }
        ])
      )
    })

    initCalendar({
      eventSources: [ {
        url: 'my-feed.php',
        className: 'customeventclass'
      } ],
      timeZone: 'America/Chicago',
      eventRender: function(arg) {
        expect(arg.el).toHaveClass('customeventclass')
        done()
      }
    })
  })

  it('requests POST correctly', function(done) {

    XHRMock.post(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({})
      expect(req.body()).toEqual('start=2014-04-27T00%3A00%3A00Z&end=2014-06-08T00%3A00%3A00Z&timeZone=UTC')
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: {
        url: 'my-feed.php',
        method: 'POST'
      },
      timeZone: 'UTC'
    })
  })

  it('accepts a extraParams object', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        timeZone: 'UTC',
        start: '2014-04-27T00:00:00Z',
        end: '2014-06-08T00:00:00Z',
        customParam: 'yes'
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      eventSources: [ {
        url: 'my-feed.php',
        extraParams: {
          customParam: 'yes'
        }
      } ]
    })
  })

  it('accepts a dynamic extraParams function', function(done) {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      expect(req.url().query).toEqual({
        timeZone: 'UTC',
        start: '2014-04-27T00:00:00Z',
        end: '2014-06-08T00:00:00Z',
        customParam: 'heckyeah'
      })
      done()
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      eventSources: [ {
        url: 'my-feed.php',
        extraParams: function() {
          return {
            customParam: 'heckyeah'
          }
        }
      } ]
    })
  })

  it('calls loading callback', function(done) {
    var loadingCallArgs = []

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: { url: 'my-feed.php' },
      loading: function(bool) {
        loadingCallArgs.push(bool)
      },
      _eventsPositioned: function() {
        expect(loadingCallArgs).toEqual([ true, false ])
        done()
      }
    })
  })

  it('has and Event Source object with certain props', function() {

    XHRMock.get(/^my-feed\.php/, function(req, res) {
      return res.status(200).header('content-type', 'application/json').body('[]')
    })

    initCalendar({
      events: { url: 'my-feed.php' }
    })
    expect(currentCalendar.getEventSources()[0].url).toBe('my-feed.php')
  })

})
<|endoftext|>


<commit_before>var React = require('react');
var ReactDOM = require('react-dom');

var CommentList = React.createClass({
  render: function(){
    return (

        Hello, world! I am a CommentList.

    );
  }
});

var CommentForm = React.createClass({
  render: function(){
    return (

        Hello, world! I am a CommentForm.

    );
  }
});

var CommentBox = React.createClass({
  render: function(){
    return (

Comments

        <CommentList />
        <CommentForm />

    );
  }
});

ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);
<commit_msg>step3 . tutorail 5/6/7. add props and marked<commit_after>var React = require('react');
var ReactDOM = require('react-dom');

var Comment = React.createClass({
  rawMarkup: function(){
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return {__html: rawMarkup};
  },
  render: function(){
    return (

          {this.props.author}

    );
  }
});

var CommentList = React.createClass({
  render: function(){
    return (

        <Comment author="Pete Hunt">This is one comment</Comment>
        <Comment author="Jordan Walke">This is *another* comment</Comment>

    );
  }
});

var CommentForm = React.createClass({
  render: function(){
    return (

        Hello, world! I am a CommentForm.

    );
  }
});

var CommentBox = React.createClass({
  render: function(){
    return (

Comments

        <CommentList />
        <CommentForm />

    );
  }
});

ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);
<|endoftext|>


<commit_before>var express = require('express');

var router = express.Router();

var Team = require('../../../models/teams');

router.delete('/:teamId', function(req, res) {

    var teamId = req.params.teamId;

    Team.findById(teamId, function(err, team) {
        if(!err) {
            // Found the team in the database now take it out
            team.remove(function(err) {
                if(!err) {
                    res.json({success: true});
                }
                else {
                    res.json({success: false, err: err.message});
                }
            });
        }
        else {
            res.json({success: false, err: err.message});
        }
    });
});

module.exports = router;
<commit_msg>Added a patch route to admin/teams/:teamId so that we can add donations to teams.<commit_after>var express = require('express');

var router = express.Router();

var Team = require('../../../models/teams');

router.delete('/:teamId', function(req, res) {

    var teamId = req.params.teamId;

    Team.findById(teamId, function(err, team) {
        if(!err) {
            // Found the team in the database now take it out
            team.remove(function(err) {
                if(!err) {
                    res.json({success: true});
                }
                else {
                    res.json({success: false, err: err.message});
                }
            });
        }
        else {
            res.json({success: false, err: err.message});
        }
    });
});

// Sending a patch request to this route indicates that we want to
// add a donation to this team
router.patch('/:teamId', function(req, res) {

    var teamId = req.params.teamId;

    var donationAmount = req.body.donationAmount;

    Team.findById(teamId, function(err, team) {

        team.dollarsDonated += donationAmount;
        team.save(function(err) {
            res.json({success: true});
        });
    });
});

module.exports = router;
<|endoftext|>


<commit_before>/**
 * 1) Open two new tabs and firebug on it with Net panel selected.
 * 2) Disable and Enable net panel.
 * 3) Perform net request on the first tab and check net panel content.
 * 4) Perform net request on the second tab and check net panel content.
 */

var tab1 = null;
var tab2 = null;

function runTest()
{
    FBTest.sysout("activation.START");

    // Open two tabs one after another, open Firebug on both and select Net panel.
    tab1 = FBTest.openNewTab(basePath + "net/activation/activation1.html", function(win) {
        FBTest.progress("Opened new tab at "+win.location);
        FBTest.openFirebug();
        FW.Firebug.chrome.selectPanel("net");
        tab2 = FBTest.openNewTab(basePath + "net/activation/activation2.html", function() {
            FBTest.progress("Opened new tab at "+win.location);
            FBTest.openFirebug();
            FW.Firebug.chrome.selectPanel("net");
            onRunTest();
        });
    });
}

function onRunTest(window)
{
    // Disable and enable
    FBTest.progress("Disable net panel");
    FBTest.disableNetPanel();
    FBTest.progress("Enable net panel");
    FBTest.enableNetPanel();

    // Select first tab, execute XHR and verify. Once it's done do the same for the other tab.
    selectTabAndVerify(tab1, function() {
        selectTabAndVerify(tab2, function() {
            FBTest.testDone("activation.DONE");
        });
    });
}

function selectTabAndVerify(tab, callback)
{
    var tabbrowser = FBTest.getBrowser();
    tabbrowser.selectedTab = tab;

    var win = tab.linkedBrowser.contentWindow;
    FBTest.progress("Selected Firefox tab "+win.location);

    var options = {
        tagName: "tr",
        classes: "netRow category-xhr hasHeaders loaded"
    };

    // Asynchronously wait for the request beeing displayed.
    FBTest.waitForDisplayedElement("net", options, function(netRow)
    {
        FBTest.ok(netRow, "There must be one xhr request.");
        callback();
    });

    FBTest.click(win.document.getElementById("testButton"));
}
<commit_msg>Fixed FBTest net/activation/activation.js<commit_after>/**
 * 1) Open two new tabs and firebug on it with Net panel selected.
 * 2) Disable and Enable net panel.
 * 3) Perform net request on the first tab and check net panel content.
 * 4) Perform net request on the second tab and check net panel content.
 */

var tab1 = null;
var tab2 = null;

function runTest()
{
    FBTest.sysout("activation.START");

    // Open two tabs one after another, open Firebug on both and select Net panel.
    tab1 = FBTest.openNewTab(basePath + "net/activation/activation1.html", function(win) {
        FBTest.progress("Opened new tab at "+win.location);
        FBTest.openFirebug();
        FW.Firebug.chrome.selectPanel("net");
        tab2 = FBTest.openNewTab(basePath + "net/activation/activation2.html", function() {
            FBTest.progress("Opened new tab at "+win.location);
            FBTest.openFirebug(function () {
                FW.Firebug.chrome.selectPanel("net");
                onRunTest();
            });
        });
    });
}

function onRunTest(window)
{
    // Disable and enable
    FBTest.progress("Disable net panel");
    FBTest.disableNetPanel();
    FBTest.progress("Enable net panel");
    FBTest.enableNetPanel();

    // Select first tab, execute XHR and verify. Once it's done do the same for the other tab.
    selectTabAndVerify(tab1, function() {
        selectTabAndVerify(tab2, function() {
            FBTest.testDone("activation.DONE");
        });
    });
}

function selectTabAndVerify(tab, callback)
{
    var tabbrowser = FBTest.getBrowser();
    tabbrowser.selectedTab = tab;

    var win = tab.linkedBrowser.contentWindow;
    FBTest.progress("Selected Firefox tab "+win.location);

    var options = {
        tagName: "tr",
        classes: "netRow category-xhr hasHeaders loaded"
    };

    // Asynchronously wait for the request beeing displayed.
    FBTest.waitForDisplayedElement("net", options, function(netRow)
    {
        FBTest.ok(netRow, "There must be one xhr request.");
        callback();
    });

    FBTest.click(win.document.getElementById("testButton"));
}
<|endoftext|>


<commit_before>const models = require('../models/index')
const insecurity = require('../lib/insecurity')

module.exports = function updateUserProfile () {
  return (req, res, next) => {
    const loggedInUser = insecurity.authenticatedUsers.get(req.cookies.token)

    let filteredUsername
    filteredUsername = req.body.username.replace(/(script|iframe|window|javascript|href|value)/ig, '')

    if (loggedInUser) {
      models.User.findByPk(loggedInUser.data.id).then(user => {
        return user.updateAttributes({ username: filteredUsername })
      }).catch(error => {
        next(error)
      })
    } else {
      next(new Error('Blocked illegal activity by ' + req.connection.remoteAddress))
    }
    res.location('/profile')
    res.redirect('/profile')
  }
}
<commit_msg>cleanup: remove presence of unneeded code<commit_after>const models = require('../models/index')
const insecurity = require('../lib/insecurity')

module.exports = function updateUserProfile () {
  return (req, res, next) => {
    const loggedInUser = insecurity.authenticatedUsers.get(req.cookies.token)

    if (loggedInUser) {
      models.User.findByPk(loggedInUser.data.id).then(user => {
        return user.updateAttributes({ username: req.body.username })
      }).catch(error => {
        next(error)
      })
    } else {
      next(new Error('Blocked illegal activity by ' + req.connection.remoteAddress))
    }
    res.location('/profile')
    res.redirect('/profile')
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014-2020 Bjoern Kimminich.
 * SPDX-License-Identifier: MIT
 */

const models = require('../models/index')
const insecurity = require('../lib/insecurity')
const utils = require('../lib/utils')
const cache = require('../data/datacache')
const challenges = cache.challenges

module.exports = function updateUserProfile () {
  return (req, res, next) => {
    const loggedInUser = insecurity.authenticatedUsers.get(req.cookies.token)

    if (loggedInUser) {
      models.User.findByPk(loggedInUser.data.id).then(user => {
        utils.solveIf(challenges.csrfChallenge, () => { return (req.headers.origin && req.headers.origin.includes('://htmledit.squarefree.com') || req.headers.referrer && req.headers.referrer.includes('://htmledit.squarefree.com')) && req.body.username !== user.username })
        return user.update({ username: req.body.username })
      }).catch(error => {
        next(error)
      })
    } else {
      next(new Error('Blocked illegal activity by ' + req.connection.remoteAddress))
    }
    res.location(process.env.BASE_PATH + '/profile')
    res.redirect(process.env.BASE_PATH + '/profile')
  }
}
<commit_msg>Fix logic condition mess in request header checks<commit_after>/*
 * Copyright (c) 2014-2020 Bjoern Kimminich.
 * SPDX-License-Identifier: MIT
 */

const models = require('../models/index')
const insecurity = require('../lib/insecurity')
const utils = require('../lib/utils')
const cache = require('../data/datacache')
const challenges = cache.challenges

module.exports = function updateUserProfile () {
  return (req, res, next) => {
    const loggedInUser = insecurity.authenticatedUsers.get(req.cookies.token)

    if (loggedInUser) {
      models.User.findByPk(loggedInUser.data.id).then(user => {
        utils.solveIf(challenges.csrfChallenge, () => {
          return ((req.headers.origin && req.headers.origin.includes('://htmledit.squarefree.com'))
            || (req.headers.referrer && req.headers.referrer.includes('://htmledit.squarefree.com')))
            && req.body.username !== user.username })
        return user.update({ username: req.body.username })
      }).catch(error => {
        next(error)
      })
    } else {
      next(new Error('Blocked illegal activity by ' + req.connection.remoteAddress))
    }
    res.location(process.env.BASE_PATH + '/profile')
    res.redirect(process.env.BASE_PATH + '/profile')
  }
}
<|endoftext|>


<commit_before>/*
 * grunt-cfpb-internal
 * http://consumerfinance.gov
 *
 * Based on grunt-contrib-less
 * http://gruntjs.com
 * Copyright (c) 2012 Tyler Kellen, contributors
 * Licensed under the MIT license.
 */

'use strict';

var shell = require('shelljs');

module.exports = function(grunt) {

  // Add custom template delimiters.
  grunt.template.addDelimiters('build-cfpb', '{%', '%}');

  grunt.registerTask('build-cfpb', 'Generate CFPB README', function() {

    var path = require('path'),
        asset = path.join.bind(null, __dirname, 'assets'),
        meta = grunt.file.readJSON('package.json');

    // Read changelog YAML
    if ( !grunt.file.exists('CHANGELOG') ) {
        grunt.file.copy( asset('CHANGELOG'), 'CHANGELOG' );
        grunt.log.ok('Created CHANGELOG file');
    }
    meta.changelog = grunt.file.readYAML('CHANGELOG');
    console.log(Object.keys(meta.changelog)[0]);

    // Read contributing markdown file
    if ( !grunt.file.exists('CONTRIBUTING.md') ) {
        grunt.file.copy( asset('CONTRIBUTING.md'), 'CONTRIBUTING.md' );
        grunt.log.ok('Created CONTRIBUTING.md');
    }
    meta.contributing = grunt.file.read('CONTRIBUTING.md');

    // Read license file
    if ( !grunt.file.exists('LICENSE') ) {
        grunt.file.copy( asset('LICENSE'), 'LICENSE' );
        grunt.log.ok('Created LICENSE file');
    }
    meta.license = grunt.file.read('LICENSE');

    // Generate readme.
    var tmpl = grunt.file.read( asset('README.tmpl.md') ),
        appendix = grunt.template.process( tmpl, {data: meta, delimiters: 'build-cfpb'} ),
        readme = grunt.file.exists('README.md') ? grunt.file.read('README.md') : '';

    // Remove contributing section and everything after from readme because we'll be re-adding it.
    // ([\s\n\r]*) selects any whitespace before the contributing title.
    // ([\s\S]*) selects everything after the contributing title.
    readme = readme.replace(/([\s\n\r]*)## Contributing([\s\S]*)/ig, '');

    grunt.file.write( 'README.md', readme + appendix );
    grunt.log.ok('Created README.md');

    // Get the most recent version from the changelog
    var version = Object.keys( meta.changelog )[0];

    // Check if the readme contains the most recent version (there's probably a better way to do this)
    if ( readme.indexOf( version ) === -1 ) {
        var pkg = grunt.file.readJSON('package.json');
        pkg.version = version.replace( 'v', '' );
        grunt.file.write( 'package.json', JSON.stringify( pkg, null, '  ' ) + '\n');
        shell.exec( 'git tag ' + version + ' -m "Version '+ version +'"', {silent:true} );
        shell.exec( 'git push --tags' );
        grunt.log.ok( 'Version bumped to ' + version );
    }

    // Fail task if any errors were logged.
    if ( this.errorCount > 0 ) { return false; }

  });

};
<commit_msg>Commit before tagging<commit_after>/*
 * grunt-cfpb-internal
 * http://consumerfinance.gov
 *
 * Based on grunt-contrib-less
 * http://gruntjs.com
 * Copyright (c) 2012 Tyler Kellen, contributors
 * Licensed under the MIT license.
 */

'use strict';

var shell = require('shelljs');

module.exports = function(grunt) {

  // Add custom template delimiters.
  grunt.template.addDelimiters('build-cfpb', '{%', '%}');

  grunt.registerTask('build-cfpb', 'Generate CFPB README', function() {

    var path = require('path'),
        asset = path.join.bind(null, __dirname, 'assets'),
        meta = grunt.file.readJSON('package.json');

    // Read the changelog YAML file.
    if ( !grunt.file.exists('CHANGELOG') ) {
        grunt.file.copy( asset('CHANGELOG'), 'CHANGELOG' );
        grunt.log.ok('Created CHANGELOG file');
    }
    meta.changelog = grunt.file.readYAML('CHANGELOG');
    console.log(Object.keys(meta.changelog)[0]);

    // Read the contributing markdown file.
    if ( !grunt.file.exists('CONTRIBUTING.md') ) {
        grunt.file.copy( asset('CONTRIBUTING.md'), 'CONTRIBUTING.md' );
        grunt.log.ok('Created CONTRIBUTING.md');
    }
    meta.contributing = grunt.file.read('CONTRIBUTING.md');

    // Read the license file.
    if ( !grunt.file.exists('LICENSE') ) {
        grunt.file.copy( asset('LICENSE'), 'LICENSE' );
        grunt.log.ok('Created LICENSE file');
    }
    meta.license = grunt.file.read('LICENSE');

    // Generate readme.
    var tmpl = grunt.file.read( asset('README.tmpl.md') ),
        appendix = grunt.template.process( tmpl, {data: meta, delimiters: 'build-cfpb'} ),
        readme = grunt.file.exists('README.md') ? grunt.file.read('README.md') : '';

    // Remove contributing section and everything after from readme because we'll be re-adding it.
    // ([\s\n\r]*) selects any whitespace before the contributing title.
    // ([\s\S]*) selects everything after the contributing title.
    readme = readme.replace(/([\s\n\r]*)## Contributing([\s\S]*)/ig, '');

    grunt.file.write( 'README.md', readme + appendix );
    grunt.log.ok('Created README.md');

    // Get the most recent version from the changelog.
    var version = Object.keys( meta.changelog )[0];

    // Check if the readme contains the most recent version (there's probably a better way to do this).
    if ( grunt.file.read('README.md').indexOf( version ) === -1 ) {

        // Get a fresh copy of package json and stringify the latest CHANGELOG entry.
        var pkg = grunt.file.readJSON('package.json'),
            msg = meta.changelog[ version ].changes.join(' ');

        // Bump the version in package.json.
        pkg.version = version.replace( 'v', '' );
        grunt.file.write( 'package.json', JSON.stringify( pkg, null, '  ' ) + '\n');

        // Commit the latest changes.
        shell.exec( 'git commit -am "' + msg + '"' );

        // Tag and push tags.
        shell.exec( 'git tag ' + version + ' -m "Version ' + version + '"', {silent:true} );
        shell.exec( 'git push --tags' );

        // Notify the user.
        grunt.log.ok( 'Version bumped to ' + version );
    }

    // Fail task if any errors were logged.
    if ( this.errorCount > 0 ) { return false; }

  });

};
<|endoftext|>


<commit_before>/*
 * grunt-imageoptim
 * https://github.com/JamieMason/grunt-imageoptim
 * Copyright © 2013 Jamie Mason, @GotNoSugarBaby,
 * https://github.com/JamieMason
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

module.exports = function(grunt) {

  'use strict';

  var q = require('q');
  var path = require('path');
  var childProcess = require('child_process');
  var exec = childProcess.exec;
  var spawn = childProcess.spawn;
  var gruntFile = path.resolve();
  var taskName = 'imageoptim';
  var issuesPage = 'https://github.com/JamieMason/grunt-imageoptim/issues/new';
  var taskDescription = 'Losslessly compress images from the command line';
  var cliPaths = [
    '../node_modules/imageoptim-cli/bin',
    '../../imageoptim-cli/bin'
  ].map(function(dir) {
    return path.resolve(__dirname, dir);
  });

  (function() {
    var config = grunt.config.data.imageoptim || {};
    var tasks = config ? [config] : [];
    var hasDeprecatedConfig = false;
    var error = '';

    Object.keys(config).forEach(function(key) {
      var value = config[key];
      var isObject = value && typeof value === 'object';
      var isTask = isObject && key !== 'options' && key !== 'files';
      if (isTask) {
        tasks.push(value);
      }
    });

    hasDeprecatedConfig = tasks.some(function(task) {
      return 'files' in task && task.files.some(function(member) {
        return typeof member === 'string';
      });
    });

    if (hasDeprecatedConfig) {
      error += 'The current grunt-imageoptim configuration format was deprecated to allow us to ';
      error += 'add full support for file pattern matching.\n';
      error += 'In most cases, all this means is renaming the "files" property to "src" but ';
      error += 'updated examples can be found at https://github.com/JamieMason/grunt-imageoptim.';
      grunt.fail.fatal(error);
    }
  }());

  /**
   * Get the ImageOptim-CLI Terminal command to be run for a given directory and task options
   * @param  {String} directory
   * @param  {Object} options
   * @return {String}
   */

  function getCommandByDirectory(directory, options) {
    var command = './imageOptim';
    command += options.quitAfter ? ' --quit' : '';
    command += options.imageAlpha ? ' --image-alpha' : '';
    command += options.jpegMini ? ' --jpeg-mini' : '';
    return command + ' --directory ' + directory.replace(/\s/g, '\\ ');
  }

  /**
   * @param  {String} command
   * @param  {String} cwd
   * @return {Promise}
   */

  function executeDirectoryCommand(command, cwd) {

    var deferred = q.defer();
    var errorMessage = 'ImageOptim-CLI exited with a failure status';
    var imageOptimCli = exec(command, {
      cwd: cwd
    });

    imageOptimCli.stdout.on('data', function(message) {
      console.log(String(message || '').replace(/\n+$/, ''));
    });

    imageOptimCli.on('exit', function(code) {
      return code === 0 ? deferred.resolve(true) : deferred.reject(new Error(errorMessage));
    });

    return deferred.promise;

  }

  /**
   * @param  {String[]}  files             Array of paths to directories from src: in config.
   * @param  {Boolean}   opts.jpegMini     Whether to run JPEGmini.app.
   * @param  {Boolean}   opts.imageAlpha   Whether to run ImageAlpha.app.
   * @param  {Boolean}   opts.quitAfter    Whether to quit apps after running.
   * @return {Promise}
   */

  function processDirectories(files, opts) {
    return files.map(function(directory) {
      return getCommandByDirectory(directory, opts);
    }).reduce(function(promise, command) {
      return promise.then(function() {
        return executeDirectoryCommand(command, opts.cliPath);
      });
    }, q());
  }

  /**
   * @param  {String[]}  files             Array of paths to files from src: in config.
   * @param  {Boolean}   opts.jpegMini     Whether to run JPEGmini.app.
   * @param  {Boolean}   opts.imageAlpha   Whether to run ImageAlpha.app.
   * @param  {Boolean}   opts.quitAfter    Whether to quit apps after running.
   * @return {Promise}
   */

  function processFiles(files, opts) {

    var imageOptimCli;
    var cliOptions = [];
    var deferred = q.defer();
    var errorMessage = 'ImageOptim-CLI exited with a failure status';

    if (opts.quitAfter) {
      cliOptions.push('--quit');
    }
    if (opts.imageAlpha) {
      cliOptions.push('--image-alpha');
    }
    if (opts.jpegMini) {
      cliOptions.push('--jpeg-mini');
    }

    imageOptimCli = spawn('./imageOptim', cliOptions, {
      cwd: opts.cliPath
    });

    imageOptimCli.stdout.on('data', function(message) {
      console.log(String(message || '').replace(/\n+$/, ''));
    });

    imageOptimCli.on('exit', function(code) {
      return code === 0 ? deferred.resolve(true) : deferred.reject(new Error(errorMessage));
    });

    imageOptimCli.stdin.setEncoding('utf8');
    imageOptimCli.stdin.end(files.join('\n'));

    return deferred.promise;

  }

  /**
   * @param  {String} str "hello"
   * @return {String}     "Hello"
   */

  function firstUp(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }

  /**
   * @param  {String}  fileType "file" or "Dir"
   * @return {Function}
   */

  function isFileType(fileType) {
    var methodName = 'is' + firstUp(fileType);
    return function(file) {
      return grunt.file[methodName](file);
    };
  }

  /**
   * Ensure the ImageOptim-CLI binary is accessible
   * @return {String}
   */

  function getPathToCli() {
    return cliPaths.filter(function(cliPath) {
      return grunt.file.exists(cliPath);
    })[0];
  }

  function toAbsolute(relativePath) {
    return path.resolve(gruntFile, relativePath);
  }

  grunt.registerMultiTask(taskName, taskDescription, function() {

    var task = this;
    var done = task.async();
    var promise = q();
    var cliPath = getPathToCli();
    var options = task.options({
      jpegMini: false,
      imageAlpha: false,
      quitAfter: false
    });

    if (!cliPath) {
      throw new Error('Unable to locate ImageOptim-CLI. Please raise issue at ' + issuesPage);
    }

    task.files.forEach(function(file) {
      var directories = file.src.filter(isFileType('dir')).map(toAbsolute);
      var files = file.src.filter(isFileType('file')).map(toAbsolute);
      if (directories.length > 0) {
        promise = promise.then(function() {
          return processDirectories(directories, {
            cliPath: cliPath,
            jpegMini: options.jpegMini,
            imageAlpha: options.imageAlpha,
            quitAfter: options.quitAfter
          });
        });
      }
      if (files.length > 0) {
        promise = promise.then(function() {
          return processFiles(files, {
            cliPath: cliPath,
            jpegMini: false, // options.jpegMini,
            imageAlpha: options.imageAlpha,
            quitAfter: options.quitAfter
          });
        });
      }
    });

    promise.done(done);

  });

};
<commit_msg>refactor gathering cli options<commit_after>/*
 * grunt-imageoptim
 * https://github.com/JamieMason/grunt-imageoptim
 * Copyright © 2013 Jamie Mason, @GotNoSugarBaby,
 * https://github.com/JamieMason
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

module.exports = function(grunt) {

  'use strict';

  var q = require('q');
  var path = require('path');
  var childProcess = require('child_process');
  var exec = childProcess.exec;
  var spawn = childProcess.spawn;
  var gruntFile = path.resolve();
  var taskName = 'imageoptim';
  var issuesPage = 'https://github.com/JamieMason/grunt-imageoptim/issues/new';
  var taskDescription = 'Losslessly compress images from the command line';
  var cliPaths = [
    '../node_modules/imageoptim-cli/bin',
    '../../imageoptim-cli/bin'
  ].map(function(dir) {
    return path.resolve(__dirname, dir);
  });

  (function() {
    var config = grunt.config.data.imageoptim || {};
    var tasks = config ? [config] : [];
    var hasDeprecatedConfig = false;
    var error = '';

    Object.keys(config).forEach(function(key) {
      var value = config[key];
      var isObject = value && typeof value === 'object';
      var isTask = isObject && key !== 'options' && key !== 'files';
      if (isTask) {
        tasks.push(value);
      }
    });

    hasDeprecatedConfig = tasks.some(function(task) {
      return 'files' in task && task.files.some(function(member) {
        return typeof member === 'string';
      });
    });

    if (hasDeprecatedConfig) {
      error += 'The current grunt-imageoptim configuration format was deprecated to allow us to ';
      error += 'add full support for file pattern matching.\n';
      error += 'In most cases, all this means is renaming the "files" property to "src" but ';
      error += 'updated examples can be found at https://github.com/JamieMason/grunt-imageoptim.';
      grunt.fail.fatal(error);
    }
  }());

  /**
   * Get the ImageOptim-CLI Terminal command to be run for a given directory and task options
   * @param  {String} directory
   * @param  {Object} options
   * @return {String}
   */

  function getCommandByDirectory(directory, options) {
    var command = './imageOptim';
    command += options.quitAfter ? ' --quit' : '';
    command += options.imageAlpha ? ' --image-alpha' : '';
    command += options.jpegMini ? ' --jpeg-mini' : '';
    return command + ' --directory ' + directory.replace(/\s/g, '\\ ');
  }

  /**
   * @param  {String} command
   * @param  {String} cwd
   * @return {Promise}
   */

  function executeDirectoryCommand(command, cwd) {

    var deferred = q.defer();
    var errorMessage = 'ImageOptim-CLI exited with a failure status';
    var imageOptimCli = exec(command, {
      cwd: cwd
    });

    imageOptimCli.stdout.on('data', function(message) {
      console.log(String(message || '').replace(/\n+$/, ''));
    });

    imageOptimCli.on('exit', function(code) {
      return code === 0 ? deferred.resolve(true) : deferred.reject(new Error(errorMessage));
    });

    return deferred.promise;

  }

  /**
   * @param  {String[]}  files             Array of paths to directories from src: in config.
   * @param  {Boolean}   opts.jpegMini     Whether to run JPEGmini.app.
   * @param  {Boolean}   opts.imageAlpha   Whether to run ImageAlpha.app.
   * @param  {Boolean}   opts.quitAfter    Whether to quit apps after running.
   * @return {Promise}
   */

  function processDirectories(files, opts) {
    return files.map(function(directory) {
      return getCommandByDirectory(directory, opts);
    }).reduce(function(promise, command) {
      return promise.then(function() {
        return executeDirectoryCommand(command, opts.cliPath);
      });
    }, q());
  }

  /**
   * include necessary command line flags based on the current task's options
   * @param  {Object} opts
   * @return {Array}
   */

  function getCliFlags(opts) {
    var cliOptions = [];
    if (opts.quitAfter) {
      cliOptions.push('--quit');
    }
    if (opts.imageAlpha) {
      cliOptions.push('--image-alpha');
    }
    if (opts.jpegMini) {
      cliOptions.push('--jpeg-mini');
    }
    return cliOptions;
  }

  /**
   * @param  {String[]}  files             Array of paths to files from src: in config.
   * @param  {Boolean}   opts.jpegMini     Whether to run JPEGmini.app.
   * @param  {Boolean}   opts.imageAlpha   Whether to run ImageAlpha.app.
   * @param  {Boolean}   opts.quitAfter    Whether to quit apps after running.
   * @return {Promise}
   */

  function processFiles(files, opts) {

    var imageOptimCli;
    var deferred = q.defer();
    var errorMessage = 'ImageOptim-CLI exited with a failure status';

    imageOptimCli = spawn('./imageOptim', getCliFlags(opts), {
      cwd: opts.cliPath
    });

    imageOptimCli.stdout.on('data', function(message) {
      console.log(String(message || '').replace(/\n+$/, ''));
    });

    imageOptimCli.on('exit', function(code) {
      return code === 0 ? deferred.resolve(true) : deferred.reject(new Error(errorMessage));
    });

    imageOptimCli.stdin.setEncoding('utf8');
    imageOptimCli.stdin.end(files.join('\n'));

    return deferred.promise;

  }

  /**
   * @param  {String} str "hello"
   * @return {String}     "Hello"
   */

  function firstUp(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }

  /**
   * @param  {String}  fileType "file" or "Dir"
   * @return {Function}
   */

  function isFileType(fileType) {
    var methodName = 'is' + firstUp(fileType);
    return function(file) {
      return grunt.file[methodName](file);
    };
  }

  /**
   * Ensure the ImageOptim-CLI binary is accessible
   * @return {String}
   */

  function getPathToCli() {
    return cliPaths.filter(function(cliPath) {
      return grunt.file.exists(cliPath);
    })[0];
  }

  function toAbsolute(relativePath) {
    return path.resolve(gruntFile, relativePath);
  }

  grunt.registerMultiTask(taskName, taskDescription, function() {

    var task = this;
    var done = task.async();
    var promise = q();
    var cliPath = getPathToCli();
    var options = task.options({
      jpegMini: false,
      imageAlpha: false,
      quitAfter: false
    });

    if (!cliPath) {
      throw new Error('Unable to locate ImageOptim-CLI. Please raise issue at ' + issuesPage);
    }

    task.files.forEach(function(file) {
      var directories = file.src.filter(isFileType('dir')).map(toAbsolute);
      var files = file.src.filter(isFileType('file')).map(toAbsolute);
      if (directories.length > 0) {
        promise = promise.then(function() {
          return processDirectories(directories, {
            cliPath: cliPath,
            jpegMini: options.jpegMini,
            imageAlpha: options.imageAlpha,
            quitAfter: options.quitAfter
          });
        });
      }
      if (files.length > 0) {
        promise = promise.then(function() {
          return processFiles(files, {
            cliPath: cliPath,
            jpegMini: false, // options.jpegMini,
            imageAlpha: options.imageAlpha,
            quitAfter: options.quitAfter
          });
        });
      }
    });

    promise.done(done);

  });

};
<|endoftext|>