<commit_before>Markup = {
	// Parses the passed markup text to a DOM tree contained in a RootTag object,
	// which can be rendered through RootTag#render. This object can be used
	// for caching. But that is not a necessity since parsing is very fast.
	parse: function(text, param) {
		if (text) {
			// Create the root tag as a container for all the other bits
			var rootTag = MarkupTag.create('root');
			var start = 0, end = 0, offset = 0;
			// Current tag:
			var tag = rootTag;
			while (start != -1) {
				start = text.indexOf('<', end + offset);
				offset = 0;
				if (start > end)
					tag.parts.push(text.substring(end, start));
				if (start >= 0) {
					end = text.indexOf('>', start) + 1;
					if (end <= start) {
						// Non-closed tag:
						tag.parts.push(text.substring(start));
						break;
					}
					var closing = text.charAt(start + 1) == '/';
					// empty = contentless tag: <tag/>
					var empty = !closing && text.charAt(end - 2) == '/';
					var definition = text.substring(start + (closing ? 2 : 1),
						end - (empty ? 2 : 1));
					// This could be something else than a tag, e.g. some code.
					// For now, the convention simply is to allow tag
					// definitions to be only one line long.
					if (/[\n\r]/.test(definition)) {
						end = start;
						// Skip the < when searching for the next tag
						offset = 1;
						continue;
					}
					// There is a special convention in place for empty tags:
					// These are interpretated as empty tags:
					// <tag/>, <tag />, <tag param />
					// Thes are not an empty tags. The / is regarded as part of
					// the parameter instead:
					// <tag param/ >, <tag param/>
					// This is to make unnamed url parameters easy to handle,
					// among other things. Detect this here:
					// If the tag definition contains white space, we have
					// parameters. If such a tag ended with / and the last char
					// is not white, it's not actually an empty tag but the / is
					// part of the parameter:
					if (empty && /\s.*[^\s]$/.test(definition)) {
						empty = false;
						definition += '/';
					}
					if (!closing || empty)
						// Opening tag, pass current tag as parent
						tag = MarkupTag.create(definition, tag, param);
					if (closing || empty) {
						// Closing tag
						var openTag = tag;
						// Walk up hierarchy until we find opening tag:
						if (!empty)
							while(openTag && openTag.name != definition)
								openTag = openTag.parent;
						if (openTag && openTag != rootTag) {
							// Activate parent tag
						 	tag = openTag.parent;
							// Add the closed tag to its parent's parts
							tag.parts.push(openTag);
						}
					}
				}
			}
			if (end > start)
				rootTag.parts.push(text.substring(end));
			return rootTag;
		}
		return null;
	},

	// Parses the passed text into a DOM tree and renders it directly.
	render: function(text, param) {
		var markup = Markup.parse(text, param);
		return markup && markup.render(param) || '';
	}
};

MarkupTag = Base.extend(new function() {

	var tags = {};

	// Private function to parse tag definitions (everything wihtout the trailing
	// '<' & '>'). This is used both when creating a tag object from a tag string
	// and when parsing _attributes definitions, in which case collectAttributes
	// is set to true and the method collects different information, see bellow.
	function parseDefinition(str, param, collectAttributes) {
		// When collectAttributes is true, list holds the array of attribute
		// definitions and lookup the lowercase attribute name lookup table.
		var name = null, list = [], attribute = null, attributes = {},
			lookup = {}, proto;
		// Match either name= parts, string parts (supporting both ' and ", and
		// escaped quotes inside), and pure value parts (in collectAttributes
		// mode these can also be attribute names without default values):
//		TODO: See which version is faster, replace or repeated calls to exec?
//		str.replace(/(\w+)=|(["'](?:[^"'\\]*(?:\\["']|\\|(?=["']))+)*["'])|(\S+)/gm, function() {
//			var match = arguments;
		for (var match; match = /(\w+)=|(["'](?:[^"'\\]*(?:\\["']|\\|(?=["']))+)*["'])|(\S+)/gm.exec(str);) {
			if (match[1]) { // attribute name
				attribute = match[1];
			} else { // string or value
				// Do not eval match[2] as it might contain line breaks which
				// will throw errors.
				var value = match[2];
				value = value
						&& value.substring(1, value.length - 1).replace(/\\/g, '')
						|| match[3];
				if (collectAttributes) {
					// When collecting _attributes, use list array to store them
					if (!attribute) {
						// attribute with no default value
						attribute = value;
						value = undefined;
					}
					list.push({ name: attribute, defaultValue: value });
					// See if there's a lowercase version, and if so, put it into
					// the list of attributes name translation lookup:
					var lower = attribute.toLowerCase();
					if (lower != attribute)
						lookup[lower] = attribute;
				} else {
					// Normal tag parsing:
					// Find tag name, and store attributes (named) and arguments
					// (unnamed)
					if (!name) {
						// The first value is the tag name. Once we know it, we
						// can determine the prototype to be used, and from there
						// the attributes definition and name translation lookup.
						name = value;
						// Render any undefined tag through the UndefinedTag.
						var store = param && tags[param.context]
							|| tags['default'];
						proto = store[name] || tags['default'][name]
							|| store['undefined'] || tags['default']['undefined'];
						// Now get the attribute name translation lookup:
						lookup = proto._attributes && proto._attributes.lookup
								|| lookup;
					} else if (attribute) {
						// named attribute. Use definition.lookup to translate to
						// mixed case name.
						attribute = lookup[attribute] || attribute;
						attributes[attribute] = value;
					} else { // unnamed argument
						list.push(value);
					}
				}
				// Reset attribute name again
				attribute = null;
			}
		}
		if (collectAttributes) {
			// Scan backwards to see wether further attributes define defaults.
			// This is needed to stop looping through attributes early once
			// no more defaults are available and unnamed arguments are used up.
			// See MarkupTag#create
			var defaultsFollow = false;
			for (var i = list.length - 1; i >= 0; i--) {
				list[i].defaultsFollow = defaultsFollow;
				defaultsFollow = defaultsFollow
						|| list[i].defaultValue !== undefined;
			}
			// Return both the attributes definition and the name lookup
			return list.length > 0 ? {
				attributes: list,
				lookup: lookup
			} : null;
		} else {
			// Normal tag parsing.
			// Return name, unnamed arguments and named attributes for further
			// scanning in MarkupTag.create, if the tag defines _attributes.
			return {
				name: name,
				proto: proto,
				arguments: list,
				attributes: attributes
			};
		}
	}

	return {
		render: function(content, param, encoder) {
			// Define in subclasses
		},

		renderChildren: function(param, encoder) {
			var buffer = new Array(this.parts.length);
			for (var i = 0, l = this.parts.length; i < l; i++) {
				var part = this.parts[i];
				if (part.render && (!param.allowedTags
							|| param.allowedTags[part.name])) {
					// This is a tag, render its children first into one content
					// string
					var content = part.renderChildren(param, encoder);
					// Now render the tag itself and place it in the resulting
					// buffer
					buffer[i] = part.render(content, param, encoder,
								this.parts[i - 1], this.parts[i + 1]);
				} else {
					// A simple string. Just encode it
					buffer[i] = encoder(this.parts[i]);
				}
			}
			return buffer.join('');
		},

		toString: function() {
			// Since parts contains both strings and tags, join calls toString on
			// each of them, resulting in automatic toString recursion for child
			// tags.
			var content = this.parts.join('');
			return '<' + this.definition + (content
					? '>' + content + '</' + this.name + '>'
					: '/>');
		},

		mergeAttributes: function(definition) {
			MarkupTag.mergeAttributes(definition, this.attributes, this.arguments);
		},

		statics: {
			extend: function(src) {
				return this.base().inject(src);
			},

			inject: function(src) {
				// Parse _attributes definition through the same tag parsing
				// mechanism parseDefinition contains special logic to produce
				// an info object for attribute / argument parsing further down
				// in MarkupTag.create().
				var attributes = src && src._attributes
						&& parseDefinition(src._attributes, null, true);
				if (attributes)
					src._attributes = attributes;
				// Now call base to inject it all.
				this.base(src);
				if (src && src._tags) {
					// Create lookup per tag name for prototype to be used.
					// TODO: What happens if only _context is injected, or
					// if _tags exists already and is modified later?
					// Should _tags be turned into an array on this.prototype,
					// and new tags added and contexts of old tags changed?
					var context = src._context || 'default';
					var store = tags[context] = tags[context] || {};
					src._tags.split(',').each(function(tag) {
						// Store attributes information and a reference to
						// prototype in tags
						store[tag] = this.prototype;
					}, this);
				}
				return this;
			},

			create: function(definition, parent, param) {
				// Parse tag definition for attributes (named)
				// and arguments (unnamed).
				var def = definition == 'root' ? { name: 'root', proto: RootTag.prototype }
						: parseDefinition(definition, param);
				// Instead of using the empty tag initializers that are a bit
				// slow through bootstrap's #initalize support, produce a pure
				// js object and then set its __proto__ field on Rhino,
				// to speed things up.
				// This hack works on Rhino but not on every browser.
				// On Browsers, you would do this instead:
				// var tag = new def.proto();
				var tag = {};
				tag.__proto__ = def.proto;
				// This part stays the same:
				tag.name = def.name;
				tag.attributes = def.attributes;
				tag.arguments = def.arguments;
				tag.parent = parent;
				tag.definition = definition;
				tag.parts = [];
				// Setup children list, and previous / next references
				tag.children = [];
				if (parent) {
					var siblings = parent.children;
					tag.previous = siblings[siblings.length - 1];
					if (tag.previous)
						tag.previous.next = tag;
					siblings.push(tag);
				}
				// Merge attribute definitions with the one from the prototype
				if (!def.proto)
					app.log('NULL: ' + definition + ' ' + Json.encode(def));
				var attributes = def.proto._attributes;
				if (attributes)
					tag.mergeAttributes(attributes);
				// Simulate calling initialize, since we're creating tags in an
				// odd way above... The difference to the normal initialize is
				// that all the fields are already set on tag, e.g. name,
				// attibutes, etc.
				if (tag.initialize)
					tag.initialize();
				return tag;
			},

			mergeAttributes: function(definition, attributes, arguments) {
				// definition is the result of parsing _attribuets definition
				// in inject. If _attributes were defined, use the attributes
				// object produced by parseDefinition in MarkupTag.inject now
				// to scan through defined named attributes and unnamed
				// arguments, and use default values if available.
				var index = 0, attribs = definition.attributes;
				for (var i = 0, l = attribs.length; i < l; i++) {
					var attrib = attribs[i];
					// If the tag does not define this predefined attribute,
					// either take its value from the unnamed arguments,
					// and increase index, or use its default value.
					if (attributes[attrib.name] === undefined) {
						// Use default value if running out of unnamed args
						attributes[attrib.name] = arguments
								&& index < arguments.length ? arguments[index++]
										: attrib.defaultValue;
					}
					// If the _attributes definition does not contain any more
					// defaults and we are running out of unnamed arguments, we
					// might as well drop out of the loop since there won't be
					// anything to be done.
					if (!attrib.defaultsFollow && (!arguments
							|| index >= arguments.length))
						break;
				}
				// Cut away consumed unnamed arguments
				if (arguments && index > 0)
					arguments.splice(0, index);
			}
		}
	}
});

// The RootTag is there to contain all other markup tags and content parts and
// is produced internally in and returned by Markup.parse. Call render on it
// to render the parsed Markup tree.
RootTag = MarkupTag.extend({
	_tags: 'root',

	// The RootTag's render function is different as it is used to render the
	// whole tree and does not receive content or encoder as parameters.
	render: function(param) {
		if (!param)
			param = {};
		if (typeof param.allowedTags == 'string') {
			var names = param.allowedTags.split(',');
			var allowed = param.allowedTags = {};
			for (var i = 0, l = names.length; i < l; i++)
				allowed[names[i]] = true;
		}
		// Determine encoder to be used, default is not encoding anything:
		var encoder = param.encoding && global['encode'
				+ param.encoding.capitalize()]
			|| function(val) { return val };
		var str = this.renderChildren(param, encoder);
		return str;
	}
});

// Special tag to render undefined tag names unmodified.
// UndefinedTag#render can be overridden and is handling the rendering of all the
// undefined tags.
UndefinedTag = MarkupTag.extend({
	_tags: 'undefined',

	render: function(content, param, encoder) {
		return encoder('<' + this.definition) + (content
				? encoder('>') + content + encoder('</' + this.name + '>')
				: encoder('/>'));
	}
});
<commit_msg>Minor clean-up.<commit_after>Markup = {
	// Parses the passed markup text to a DOM tree contained in a RootTag object,
	// which can be rendered through RootTag#render. This object can be used
	// for caching. But that is not a necessity since parsing is very fast.
	parse: function(text, param) {
		if (text) {
			// Create the root tag as a container for all the other bits
			var rootTag = MarkupTag.create('root');
			var start = 0, end = 0, offset = 0;
			// Current tag:
			var tag = rootTag;
			while (start != -1) {
				start = text.indexOf('<', end + offset);
				offset = 0;
				if (start > end)
					tag.parts.push(text.substring(end, start));
				if (start >= 0) {
					end = text.indexOf('>', start) + 1;
					if (end <= start) {
						// Non-closed tag:
						tag.parts.push(text.substring(start));
						break;
					}
					var closing = text.charAt(start + 1) == '/';
					// empty = contentless tag: <tag/>
					var empty = !closing && text.charAt(end - 2) == '/';
					var definition = text.substring(start + (closing ? 2 : 1),
						end - (empty ? 2 : 1));
					// This could be something else than a tag, e.g. some code.
					// For now, the convention simply is to allow tag
					// definitions to be only one line long.
					if (/[\n\r]/.test(definition)) {
						end = start;
						// Skip the < when searching for the next tag
						offset = 1;
						continue;
					}
					// There is a special convention in place for empty tags:
					// These are interpretated as empty tags:
					// <tag/>, <tag />, <tag param />
					// Thes are not an empty tags. The / is regarded as part of
					// the parameter instead:
					// <tag param/ >, <tag param/>
					// This is to make unnamed url parameters easy to handle,
					// among other things. Detect this here:
					// If the tag definition contains white space, we have
					// parameters. If such a tag ended with / and the last char
					// is not white, it's not actually an empty tag but the / is
					// part of the parameter:
					if (empty && /\s.*[^\s]$/.test(definition)) {
						empty = false;
						definition += '/';
					}
					if (!closing || empty)
						// Opening tag, pass current tag as parent
						tag = MarkupTag.create(definition, tag, param);
					if (closing || empty) {
						// Closing tag
						var openTag = tag;
						// Walk up hierarchy until we find opening tag:
						if (!empty)
							while(openTag && openTag.name != definition)
								openTag = openTag.parent;
						if (openTag && openTag != rootTag) {
							// Activate parent tag
						 	tag = openTag.parent;
							// Add the closed tag to its parent's parts
							tag.parts.push(openTag);
						}
					}
				}
			}
			if (end > start)
				rootTag.parts.push(text.substring(end));
			return rootTag;
		}
		return null;
	},

	// Parses the passed text into a DOM tree and renders it directly.
	render: function(text, param) {
		var markup = Markup.parse(text, param);
		return markup && markup.render(param) || '';
	}
};

MarkupTag = Base.extend(new function() {

	var tags = {};

	// Private function to parse tag definitions (everything wihtout the trailing
	// '<' & '>'). This is used both when creating a tag object from a tag string
	// and when parsing _attributes definitions, in which case collectAttributes
	// is set to true and the method collects different information, see bellow.
	function parseDefinition(str, param, collectAttributes) {
		// When collectAttributes is true, list holds the array of attribute
		// definitions and lookup the lowercase attribute name lookup table.
		var name = null, list = [], attribute = null, attributes = {},
			lookup = {}, proto;
		// Match either name= parts, string parts (supporting both ' and ", and
		// escaped quotes inside), and pure value parts (in collectAttributes
		// mode these can also be attribute names without default values):
//		TODO: See which version is faster, replace or repeated calls to exec?
//		str.replace(/(\w+)=|(["'](?:[^"'\\]*(?:\\["']|\\|(?=["']))+)*["'])|(\S+)/gm, function() {
//			var match = arguments;
		for (var match; match = /(\w+)=|(["'](?:[^"'\\]*(?:\\["']|\\|(?=["']))+)*["'])|(\S+)/gm.exec(str);) {
			if (match[1]) { // attribute name
				attribute = match[1];
			} else { // string or value
				// Do not eval match[2] as it might contain line breaks which
				// will throw errors.
				var value = match[2];
				value = value
						&& value.substring(1, value.length - 1).replace(/\\/g, '')
						|| match[3];
				if (collectAttributes) {
					// When collecting _attributes, use list array to store them
					if (!attribute) {
						// attribute with no default value
						attribute = value;
						value = undefined;
					}
					list.push({ name: attribute, defaultValue: value });
					// See if there's a lowercase version, and if so, put it into
					// the list of attributes name translation lookup:
					var lower = attribute.toLowerCase();
					if (lower != attribute)
						lookup[lower] = attribute;
				} else {
					// Normal tag parsing:
					// Find tag name, and store attributes (named) and arguments
					// (unnamed)
					if (!name) {
						// The first value is the tag name. Once we know it, we
						// can determine the prototype to be used, and from there
						// the attributes definition and name translation lookup.
						name = value;
						// Render any undefined tag through the UndefinedTag.
						var store = param && tags[param.context]
							|| tags['default'];
						proto = store[name] || tags['default'][name]
							|| store['undefined'] || tags['default']['undefined'];
						// Now get the attribute name translation lookup:
						lookup = proto._attributes && proto._attributes.lookup
								|| lookup;
					} else if (attribute) {
						// named attribute. Use definition.lookup to translate to
						// mixed case name.
						attribute = lookup[attribute] || attribute;
						attributes[attribute] = value;
					} else { // unnamed argument
						list.push(value);
					}
				}
				// Reset attribute name again
				attribute = null;
			}
		}
		if (collectAttributes) {
			// Scan backwards to see wether further attributes define defaults.
			// This is needed to stop looping through attributes early once
			// no more defaults are available and unnamed arguments are used up.
			// See MarkupTag#create
			var defaultsFollow = false;
			for (var i = list.length - 1; i >= 0; i--) {
				list[i].defaultsFollow = defaultsFollow;
				defaultsFollow = defaultsFollow
						|| list[i].defaultValue !== undefined;
			}
			// Return both the attributes definition and the name lookup
			return list.length > 0 ? {
				attributes: list,
				lookup: lookup
			} : null;
		} else {
			// Normal tag parsing.
			// Return name, unnamed arguments and named attributes for further
			// scanning in MarkupTag.create, if the tag defines _attributes.
			return {
				name: name,
				proto: proto,
				arguments: list,
				attributes: attributes
			};
		}
	}

	return {
		render: function(content, param, encoder) {
			// Define in subclasses
		},

		renderChildren: function(param, encoder) {
			var buffer = new Array(this.parts.length);
			for (var i = 0, l = this.parts.length; i < l; i++) {
				var part = this.parts[i];
				if (part.render && (!param.allowedTags
							|| param.allowedTags[part.name])) {
					// This is a tag, render its children first into one content
					// string
					var content = part.renderChildren(param, encoder);
					// Now render the tag itself and place it in the resulting
					// buffer
					buffer[i] = part.render(content, param, encoder,
								this.parts[i - 1], this.parts[i + 1]);
				} else {
					// A simple string. Just encode it
					buffer[i] = encoder(this.parts[i]);
				}
			}
			return buffer.join('');
		},

		toString: function() {
			// Since parts contains both strings and tags, join calls toString on
			// each of them, resulting in automatic toString recursion for child
			// tags.
			var content = this.parts.join('');
			return '<' + this.definition + (content
					? '>' + content + '</' + this.name + '>'
					: '/>');
		},

		mergeAttributes: function(definition) {
			MarkupTag.mergeAttributes(definition, this.attributes, this.arguments);
		},

		statics: {
			extend: function(src) {
				return this.base().inject(src);
			},

			inject: function(src) {
				// Parse _attributes definition through the same tag parsing
				// mechanism parseDefinition contains special logic to produce
				// an info object for attribute / argument parsing further down
				// in MarkupTag.create().
				var attributes = src && src._attributes
						&& parseDefinition(src._attributes, null, true);
				if (attributes)
					src._attributes = attributes;
				// Now call base to inject it all.
				this.base(src);
				if (src && src._tags) {
					// Create lookup per tag name for prototype to be used.
					// TODO: What happens if only _context is injected, or
					// if _tags exists already and is modified later?
					// Should _tags be turned into an array on this.prototype,
					// and new tags added and contexts of old tags changed?
					var context = src._context || 'default';
					var store = tags[context] = tags[context] || {};
					src._tags.split(',').each(function(tag) {
						// Store attributes information and a reference to
						// prototype in tags
						store[tag] = this.prototype;
					}, this);
				}
				return this;
			},

			create: function(definition, parent, param) {
				// Parse tag definition for attributes (named)
				// and arguments (unnamed).
				var def = definition == 'root'
						? { name: 'root', proto: RootTag.prototype }
						: parseDefinition(definition, param);
				// Instead of using the empty tag initializers that are a bit
				// slow through bootstrap's #initalize support, produce a pure
				// js object and then set its __proto__ field on Rhino,
				// to speed things up.
				// This hack works on Rhino but not on every browser.
				// On Browsers, you would do this instead:
				// var tag = new def.proto();
				var tag = {};
				tag.__proto__ = def.proto;
				// This part stays the same:
				tag.name = def.name;
				tag.attributes = def.attributes;
				tag.arguments = def.arguments;
				tag.parent = parent;
				tag.definition = definition;
				tag.parts = [];
				// Setup children list, and previous / next references
				tag.children = [];
				if (parent) {
					var siblings = parent.children;
					tag.previous = siblings[siblings.length - 1];
					if (tag.previous)
						tag.previous.next = tag;
					siblings.push(tag);
				}
				// Merge attribute definitions with the one from the prototype
				if (!def.proto)
					app.log('NULL: ' + definition + ' ' + Json.encode(def));
				var attributes = def.proto._attributes;
				if (attributes)
					tag.mergeAttributes(attributes);
				// Simulate calling initialize, since we're creating tags in an
				// odd way above... The difference to the normal initialize is
				// that all the fields are already set on tag, e.g. name,
				// attibutes, etc.
				if (tag.initialize)
					tag.initialize();
				return tag;
			},

			mergeAttributes: function(definition, attributes, arguments) {
				// definition is the result of parsing _attribuets definition
				// in inject. If _attributes were defined, use the attributes
				// object produced by parseDefinition in MarkupTag.inject now
				// to scan through defined named attributes and unnamed
				// arguments, and use default values if available.
				var index = 0, attribs = definition.attributes;
				for (var i = 0, l = attribs.length; i < l; i++) {
					var attrib = attribs[i];
					// If the tag does not define this predefined attribute,
					// either take its value from the unnamed arguments,
					// and increase index, or use its default value.
					if (attributes[attrib.name] === undefined) {
						// Use default value if running out of unnamed args
						attributes[attrib.name] = arguments
								&& index < arguments.length ? arguments[index++]
										: attrib.defaultValue;
					}
					// If the _attributes definition does not contain any more
					// defaults and we are running out of unnamed arguments, we
					// might as well drop out of the loop since there won't be
					// anything to be done.
					if (!attrib.defaultsFollow && (!arguments
							|| index >= arguments.length))
						break;
				}
				// Cut away consumed unnamed arguments
				if (arguments && index > 0)
					arguments.splice(0, index);
			}
		}
	}
});

// The RootTag is there to contain all other markup tags and content parts and
// is produced internally in and returned by Markup.parse. Call render on it
// to render the parsed Markup tree.
RootTag = MarkupTag.extend({
	_tags: 'root',

	// The RootTag's render function is different as it is used to render the
	// whole tree and does not receive content or encoder as parameters.
	render: function(param) {
		if (!param)
			param = {};
		if (typeof param.allowedTags == 'string') {
			var names = param.allowedTags.split(',');
			var allowed = param.allowedTags = {};
			for (var i = 0, l = names.length; i < l; i++)
				allowed[names[i]] = true;
		}
		// Determine encoder to be used, default is not encoding anything:
		var encoder = param.encoding && global['encode'
				+ param.encoding.capitalize()]
			|| function(val) { return val };
		var str = this.renderChildren(param, encoder);
		return str;
	}
});

// Special tag to render undefined tag names unmodified.
// UndefinedTag#render can be overridden and is handling the rendering of all the
// undefined tags.
UndefinedTag = MarkupTag.extend({
	_tags: 'undefined',

	render: function(content, param, encoder) {
		return encoder('<' + this.definition) + (content
				? encoder('>') + content + encoder('</' + this.name + '>')
				: encoder('/>'));
	}
});
<|endoftext|>


<commit_before>import React from 'react';
import moment from 'moment';
import Appointment from './components/Individual';
import Layout from '../../components/Layout';
import { listOfServices, mapOfServices } from '../../data/database/services';

async function upsertAppointment(fetch, input) {
  const {
    duration,
    id,
    name,
    mobile,
    resourceName,
    startDate,
    startTime,
    serviceIds,
    totalAmount,
    additional,
    discount,
  } = input;

  const dateInput = moment(startDate).format('YYYYMMDD');
  const timeInput = moment(startTime).format('HHmm');

  const resp = await fetch('/graphql', {
    body: JSON.stringify({
      query: `mutation($id: String!, $name: String!, $mobile:String!, $resourceName:String, $start:String!, $serviceIds:[String]!, $duration:Int!, $totalAmount:Float, $additional:Float, $discount:Float) {
            updateAppointment(id:$id, name:$name, mobile:$mobile, resourceName:$resourceName, start:$start, serviceIds:$serviceIds, duration:$duration, totalAmount:$totalAmount, additional:$additional, discount:$discount ) {
                id
                event {
                    id,
                    name
                    mobile,
                    start,
                    end,
                    serviceIds
                }
                transaction {
                    id,
                    items { id, type, name, price },
                   totalAmount,
                    service,
                    product,
                    discount,
                    additional
                }
            }
            }`,
      variables: JSON.stringify({
        id,
        name,
        mobile,
        resourceName,
        start: `${dateInput}T${timeInput}`,
        serviceIds,
        duration,
        totalAmount,
        additional,
        discount,
      }),
    }),
  });

  const { data } = await resp.json();

  return data;
}

async function listContacts(fetch) {
  const resp = await fetch('/graphql', {
    body: JSON.stringify({
      query: '{contact{name,mobile,display,resourceName}}',
    }),
  });
  const { data } = await resp.json();
  return data;
}

async function getAppointment(fetch, apptId) {
  const resp = await fetch('/graphql', {
    body: JSON.stringify({
      query: `
            {appointment(id: "${apptId}"){
                id,
                event {
                    id,
                    name
                    mobile,
                    start,
                    end,
                    serviceIds
                },
                transaction {
                    id,
                    items { id, type, name, price },
                    totalAmount,
                    service,
                    product,
                    discount,
                    additional
                }
            }}`,
    }),
  });
  const { data } = await resp.json();
  // console.log(`data=${JSON.stringify(data, null, 2)}`);
  return data.appointment;
}

async function action({ fetch, params }) {
  const apptId = params.id;
  const data = await listContacts(fetch);
  const appointment = await getAppointment(fetch, apptId);
  // // console.log(Promise.all);
  //   const [event, transaction] = Promise.all([eventP, transactionP]);
  const { event, transaction } = appointment;
  const name = event.name;
  const mobile = event.mobile;
  const startDate = moment(event.start);
  const endDate = moment(event.end);
  const duration = Number(moment.duration(endDate - startDate) / 60000);
  const serviceIds = event.serviceIds;
  const resourceName = event.resourceName;

  let discount = 0;
  let additional = 0;
  let totalAmount = 0;
  if (transaction) {
    discount = transaction.discount;
    additional = transaction.additional;
    totalAmount = transaction.totalAmount;
  }

  // contact, name, mobile, startDate, startTime, duration, serviceIds, resourceName, id, discount, additional
  //

  if (!data || !data.contact)
    throw new Error('Failed to load the contact feed.');

  return {
    chunks: ['appointment-edit'],
    title: 'Rare Beauty Professional',
    component: (
      <Layout>
        <Appointment
          post={input => {
            upsertAppointment(
              fetch,
              Object.assign({ id: apptId, resourceName }, input),
            );
            open(location, '_self').close();
          }}
          listOfServices={listOfServices}
          mapOfServices={mapOfServices}
          contact={data.contact}
          name={name}
          mobile={mobile}
          startDate={startDate.toDate()}
          startTime={startDate.toDate()}
          serviceIds={serviceIds}
          discount={discount}
          additional={additional}
          totalAmount={totalAmount}
          duration={duration}
          buttonText={'Update Appointment'}
          successMessage={'Appointment Updated'}
          {...this.props}
        />
      </Layout>
    ),
  };
}

export default action;
<commit_msg>wait before close<commit_after>import React from 'react';
import moment from 'moment';
import Appointment from './components/Individual';
import Layout from '../../components/Layout';
import { listOfServices, mapOfServices } from '../../data/database/services';

async function upsertAppointment(fetch, input) {
  const {
    duration,
    id,
    name,
    mobile,
    resourceName,
    startDate,
    startTime,
    serviceIds,
    totalAmount,
    additional,
    discount,
  } = input;

  const dateInput = moment(startDate).format('YYYYMMDD');
  const timeInput = moment(startTime).format('HHmm');

  const resp = await fetch('/graphql', {
    body: JSON.stringify({
      query: `mutation($id: String!, $name: String!, $mobile:String!, $resourceName:String, $start:String!, $serviceIds:[String]!, $duration:Int!, $totalAmount:Float, $additional:Float, $discount:Float) {
            updateAppointment(id:$id, name:$name, mobile:$mobile, resourceName:$resourceName, start:$start, serviceIds:$serviceIds, duration:$duration, totalAmount:$totalAmount, additional:$additional, discount:$discount ) {
                id
                event {
                    id,
                    name
                    mobile,
                    start,
                    end,
                    serviceIds
                }
                transaction {
                    id,
                    items { id, type, name, price },
                   totalAmount,
                    service,
                    product,
                    discount,
                    additional
                }
            }
            }`,
      variables: JSON.stringify({
        id,
        name,
        mobile,
        resourceName,
        start: `${dateInput}T${timeInput}`,
        serviceIds,
        duration,
        totalAmount,
        additional,
        discount,
      }),
    }),
  });

  const { data } = await resp.json();

  return data;
}

async function listContacts(fetch) {
  const resp = await fetch('/graphql', {
    body: JSON.stringify({
      query: '{contact{name,mobile,display,resourceName}}',
    }),
  });
  const { data } = await resp.json();
  return data;
}

async function getAppointment(fetch, apptId) {
  const resp = await fetch('/graphql', {
    body: JSON.stringify({
      query: `
            {appointment(id: "${apptId}"){
                id,
                event {
                    id,
                    name
                    mobile,
                    start,
                    end,
                    serviceIds
                },
                transaction {
                    id,
                    items { id, type, name, price },
                    totalAmount,
                    service,
                    product,
                    discount,
                    additional
                }
            }}`,
    }),
  });
  const { data } = await resp.json();
  // console.log(`data=${JSON.stringify(data, null, 2)}`);
  return data.appointment;
}

async function action({ fetch, params }) {
  const apptId = params.id;
  const data = await listContacts(fetch);
  const appointment = await getAppointment(fetch, apptId);
  // // console.log(Promise.all);
  //   const [event, transaction] = Promise.all([eventP, transactionP]);
  const { event, transaction } = appointment;
  const name = event.name;
  const mobile = event.mobile;
  const startDate = moment(event.start);
  const endDate = moment(event.end);
  const duration = Number(moment.duration(endDate - startDate) / 60000);
  const serviceIds = event.serviceIds;
  const resourceName = event.resourceName;

  let discount = 0;
  let additional = 0;
  let totalAmount = 0;
  if (transaction) {
    discount = transaction.discount;
    additional = transaction.additional;
    totalAmount = transaction.totalAmount;
  }

  // contact, name, mobile, startDate, startTime, duration, serviceIds, resourceName, id, discount, additional
  //

  if (!data || !data.contact)
    throw new Error('Failed to load the contact feed.');

  return {
    chunks: ['appointment-edit'],
    title: 'Rare Beauty Professional',
    component: (
      <Layout>
        <Appointment
          post={async (input) => {
            await upsertAppointment(
              fetch,
              Object.assign({ id: apptId, resourceName }, input)
            );
            open(location, '_self').close();
          }}
          listOfServices={listOfServices}
          mapOfServices={mapOfServices}
          contact={data.contact}
          name={name}
          mobile={mobile}
          startDate={startDate.toDate()}
          startTime={startDate.toDate()}
          serviceIds={serviceIds}
          discount={discount}
          additional={additional}
          totalAmount={totalAmount}
          duration={duration}
          buttonText={'Update Appointment'}
          successMessage={'Appointment Updated'}
          {...this.props}
        />
      </Layout>
    ),
  };
}

export default action;
<|endoftext|>


<commit_before>// Module: task controller app using angular js. You need to keep this to use it in your app as per licence agreement.
// Author Sateesh Peetha
// BLog: http://tech.dingg.org
// Connect with me on LinkedIn at https://www.linkedin.com/in/sateeshpeetha/
// or at angel.co here https://angel.co/sateesh-peetha

var app = angular.module('myApp', []);

//app.controller('TaskCtrl',

function TaskCtrl($scope, $element, $attrs) {
  $scope.taskList = [{
    taskText: 'Eat Breakfast',
    done: false,
    edit: false
  }];
  $scope.Error = false;

  $scope.taskAdd = function() {

    $scope.isDuplicate($scope.taskInput, function(dup) {

      if (dup) {
        $scope.Error = true;
        $scope.taskInput = "";

      } else {
        $scope.taskList.push({
          taskText: $scope.taskInput,
          done: false,
          edit: false
        });
        $scope.taskInput = "";
      }
    });

  }; // end of taskAdd

  $scope.isDuplicate = function(inp, callback) {

      var len = $scope.taskList.length;

      var counter = 0;
      var dup = false;

      if (len === 0)
        callback(false);
      else {
        angular.forEach($scope.taskList, function(l) {

          if (l.taskText.toLowerCase() == inp.toLowerCase()) {
            dup = true;
            callback(true);

          } else {
            if (counter == (len - 1) && dup === false) {

              callback(false);
            }
          }
          counter++;

        });
      }

    }; // end of Is Duplicate

  $scope.isUpdDuplicate = function(inp, callback) {

      var len = $scope.taskList.length;

      var counter = 0;
      var dup = false;

      if (len === 0)
        callback(false);
      else {
        angular.forEach($scope.taskList, function(l) {

          if ((l.taskText.toLowerCase() == inp.taskText.toLowerCase()) && (l.$$hashKey != inp.$$hashKey)) {

            dup = true;
            callback(true);

          } else {
            if (counter == (len - 1) && dup === false) {

              callback(false);
            }
          }
          counter++;

        });
      }

    }; // end of Is update Duplicate

  $scope.remove = function() {
    var oldList = $scope.taskList;
    $scope.taskList = [];
    angular.forEach(oldList, function(x) {

      if (!x.done) $scope.taskList.push(x);
    });
    $scope.masterSelect = false;
  }; // end of remove

  $scope.edit = function() {

    var oldList = $scope.taskList;
    $scope.taskList = [];
    angular.forEach(oldList, function(x) {

      if (x.done) {
        $scope.taskList.push(x);
        x.edit = true;
        x.old = x.taskText;
      }

      if (!x.done) $scope.taskList.push(x);

    });

  }; // end of edit

  $scope.selectAll = function(checkbox) {

    var oldList = $scope.taskList;
    $scope.taskList = [];
    angular.forEach(oldList, function(x) {

      x.done = checkbox.masterSelect;

      $scope.taskList.push(x);

    });

  }; // end of  update checkboxes

  $scope.editUpdate = function(task) {

    if (event.which == 13 && task.x.taskText !== "") {
      $scope.isUpdDuplicate(task.x, function(dup) {

        if (dup) {
          $scope.Error = true;
          $scope.taskInput = "";

        } else {
          task.x.edit = false;
          task.x.done = false;
          $scope.Error = false;
        }
      });
      $scope.masterSelect = false;

    }

    if (task.x.taskText === "") {

      $scope.Error = true;
    }
  }; // end of inline edit.

}

//);

app.component('taskCt', {

  templateUrl: 'task.html',
  controller: TaskCtrl

});
<commit_msg>Create task.js<commit_after>// Module: task controller app using angular js. You need to keep this to use it in your app as per licence agreement.
// Author Sateesh Peetha
// BLog: http://tech.dingg.org
// Connect with me on LinkedIn at https://www.linkedin.com/in/sateeshpeetha/
// or at angel.co here https://angel.co/sateesh-peetha

var app = angular.module('myApp', []);

//app.controller('TaskCtrl',

function TaskCtrl($scope, $element, $attrs) {
  $scope.taskList = [{
    taskText: 'Eat Breakfast',
    done: false,
    edit: false
  }];
  $scope.Error = false;

  $scope.taskAdd = function() {

    $scope.isDuplicate($scope.taskInput, function(dup) {

      if (dup) {
        $scope.Error = true;
        $scope.taskInput = "";

      } else {
        $scope.taskList.push({
          taskText: $scope.taskInput,
          done: false,
          edit: false
        });
        $scope.taskInput = "";
      }
    });

  }; // end of taskAdd

  $scope.isDuplicate = function(inp, callback) {

      var len = $scope.taskList.length;

      var counter = 0;
      var dup = false;

      if (len === 0)
        callback(false);
      else {
        angular.forEach($scope.taskList, function(l) {

          if (l.taskText.toLowerCase() == inp.toLowerCase()) {
            dup = true;
            callback(true);

          } else {
            if (counter == (len - 1) && dup === false) {

              callback(false);
            }
          }
          counter++;

        });
      }

    }; // end of Is Duplicate

  $scope.isUpdDuplicate = function(inp, callback) {

      var len = $scope.taskList.length;

      var counter = 0;
      var dup = false;

      if (len === 0)
        callback(false);
      else {
        angular.forEach($scope.taskList, function(l) {

          if ((l.taskText.toLowerCase() == inp.taskText.toLowerCase()) && (l.$$hashKey != inp.$$hashKey)) {

            dup = true;
            callback(true);

          } else {
            if (counter == (len - 1) && dup === false) {

              callback(false);
            }
          }
          counter++;

        });
      }

    }; // end of Is update Duplicate

  $scope.remove = function() {
    var oldList = $scope.taskList;
    $scope.taskList = [];
    angular.forEach(oldList, function(x) {

      if (!x.done) $scope.taskList.push(x);
    });
    $scope.masterSelect = false;
  }; // end of remove

  $scope.edit = function() {

    var oldList = $scope.taskList;
    $scope.taskList = [];
    angular.forEach(oldList, function(x) {

      if (x.done) {
        $scope.taskList.push(x);
        x.edit = true;
        x.old = x.taskText;
      }

      if (!x.done) $scope.taskList.push(x);

    });

  }; // end of edit

  $scope.selectAll = function(checkbox) {

    var oldList = $scope.taskList;
    $scope.taskList = [];
    angular.forEach(oldList, function(x) {

      x.done = checkbox.masterSelect;

      $scope.taskList.push(x);

    });

  }; // end of  update checkboxes

  $scope.editUpdate = function(task) {

    console.log(task);

    if (event.which == 13 && task.x.taskText !== "") {
      $scope.isUpdDuplicate(task.x, function(dup) {

        if (dup) {
          $scope.Error = true;
          $scope.taskInput = "";

        } else {
          task.x.edit = false;
          task.x.done = false;
          $scope.Error = false;
        }
      });
      $scope.masterSelect = false;

    }

    if (task.x.taskText === "") {

      $scope.Error = true;
    }
  }; // end of inline edit.

}

//);

app.component('taskCt', {

  templateUrl: 'task.html',
  controller: TaskCtrl

});
<|endoftext|>


<commit_before>'use strict';

var stdio = require('stdio'),
	fs = require('fs'),
	fse = require('fs-extra');

(function() {

	if (process.argv.slice(2).indexOf('-v') !== -1) {
		var pkg = require('../../package.json');
		console.log(pkg.name + ' ' + pkg.version);
		return;
	}

	var ops = stdio.getopt({
		'version': {key: 'v', description: 'output the version number'},
		'name': {key: 'n', args: 1, mandatory: true, description: 'application name'},
		'dir': {key: 'd', args: 1, description: 'path to the webroot directory  (defaults to /web)'},
		'port': {key: 'p', args: 1, description: 'server port number  (defaults 80)'},
		'uri': {key: 'u', args: 1, description: 'default routing path/uri (defaults /app)'},
		'routers': {key: 'r', args: 1, description: 'path to the directory with routing files (defaults to /routers)'},
		'locales': {key: 'l', args: 1, description: 'path to the directory with localization files (defaults to /locales)'},
		'env': {key: 'e', args: 1, description: 'software environment (dev/prod)'}
	});

	var lines = fs.readFileSync(__dirname + '/package.json', 'utf-8').replace('{{name}}', ops.name),
		dir = '.';
	createFile(dir, '/package.json', lines);

	lines = fs.readFileSync(__dirname + '/index.js', 'utf-8');
	createFile(dir, '/index.js', lines);

	lines = fs.readFileSync(__dirname + '/app.json', 'utf-8').
						replace('{{env}}', ops.env || 'dev').
						replace('{{port}}', ops.port || 80).
						replace('{{appdir}}', ops.dir || '/web').
						replace('{{locales}}', ops.locales || '/locales').
						replace('{{routers}}', ops.routers || '/routers');
	createFile(dir + '/config', '/app.json', lines);

	dir = '.' + (ops.locales || '/locales') + '/en';
	lines = fs.readFileSync(__dirname + '/locales.json', 'utf-8').
						replace('{{name}}', ops.name).
						replace('{{year}}', new Date().getFullYear());
	createFile(dir, '/common.json', lines);

	dir = '.' + (ops.routers || '/routers');
	lines = fs.readFileSync(__dirname + '/router.js', 'utf-8').
						replace('{{uri}}', ops.uri || '/app').
						replace('{{routers}}', ops.routers || '/routers');
	createFile(dir, '/router.js', lines);

	dir = dir + '/controllers';
	lines = fs.readFileSync(__dirname + '/controller.js', 'utf-8');
	createFile(dir, '/controller.js', lines);
	fse.copySync(__dirname + '/web', './web');

	console.log('\nThank you for using IndigoJS!');

	console.log('\nPlease run commands:');
	console.log('npm install');
	console.log('npm start');
	console.log('\nURL: http://localhost:%s%s/index', ops.port || 80, ops.uri || '/app');
})();

function createFile(dir, file, lines) {
	fse.mkdirsSync(dir);
	console.log('creating %s%s', dir, file);
	fs.writeFileSync(dir + file, lines);
}<commit_msg>added gitignore<commit_after>'use strict';

var stdio = require('stdio'),
	fs = require('fs'),
	fse = require('fs-extra');

(function() {

	if (process.argv.slice(2).indexOf('-v') !== -1) {
		var pkg = require('../../package.json');
		console.log(pkg.name + ' ' + pkg.version);
		return;
	}

	var ops = stdio.getopt({
		'version': {key: 'v', description: 'output the version number'},
		'name': {key: 'n', args: 1, mandatory: true, description: 'application name'},
		'dir': {key: 'd', args: 1, description: 'path to the webroot directory  (defaults to /web)'},
		'port': {key: 'p', args: 1, description: 'server port number  (defaults 80)'},
		'uri': {key: 'u', args: 1, description: 'default routing path/uri (defaults /app)'},
		'routers': {key: 'r', args: 1, description: 'path to the directory with routing files (defaults to /routers)'},
		'locales': {key: 'l', args: 1, description: 'path to the directory with localization files (defaults to /locales)'},
		'env': {key: 'e', args: 1, description: 'software environment (dev/prod)'}
	});

	var lines = fs.readFileSync(__dirname + '/package.json', 'utf-8').replace('{{name}}', ops.name),
		dir = '.';
	createFile(dir, '/package.json', lines);

	lines = fs.readFileSync(__dirname + '/index.js', 'utf-8');
	createFile(dir, '/index.js', lines);

	lines = fs.readFileSync(__dirname + '/.npmignore', 'utf-8');
	createFile(dir, '/.npmignore', lines);

	lines = fs.readFileSync(__dirname + '/app.json', 'utf-8').
						replace('{{env}}', ops.env || 'dev').
						replace('{{port}}', ops.port || 80).
						replace('{{appdir}}', ops.dir || '/web').
						replace('{{locales}}', ops.locales || '/locales').
						replace('{{routers}}', ops.routers || '/routers');
	createFile(dir + '/config', '/app.json', lines);

	dir = '.' + (ops.locales || '/locales') + '/en';
	lines = fs.readFileSync(__dirname + '/locales.json', 'utf-8').
						replace('{{name}}', ops.name).
						replace('{{year}}', new Date().getFullYear());
	createFile(dir, '/common.json', lines);

	dir = '.' + (ops.routers || '/routers');
	lines = fs.readFileSync(__dirname + '/router.js', 'utf-8').
						replace('{{uri}}', ops.uri || '/app').
						replace('{{routers}}', ops.routers || '/routers');
	createFile(dir, '/router.js', lines);

	dir = dir + '/controllers';
	lines = fs.readFileSync(__dirname + '/controller.js', 'utf-8');
	createFile(dir, '/controller.js', lines);
	fse.copySync(__dirname + '/web', './web');

	console.log('\nThank you for using IndigoJS!');

	console.log('\nPlease run commands:');
	console.log('npm install');
	console.log('npm start');
	console.log('\nURL: http://localhost:%s%s/index', ops.port || 80, ops.uri || '/app');
})();

function createFile(dir, file, lines) {
	fse.mkdirsSync(dir);
	console.log('creating %s%s', dir, file);
	fs.writeFileSync(dir + file, lines);
}<|endoftext|>


<commit_before>"use strict";

describe("state", function() {
  var mockConfigurator = {
    configurationFor: function(name) {
      return {};
    },
    getRetrievers: function(name) {
      return {};
    }
  };

  var createTokens = function() {
    return {
      '01': {
        id: '01',
        string: 'Arma',
        head: {
          id: '03'
        }
      },
      '02': {
        id: '02',
        string: 'virum',
        head: {
          id: '03',
        }
      },
      '03': {
        id: '03',
        string: '-que',
        head: {
          id: '04'
        }
      },
      '04': {
        id: '04',
        string: 'cano',
        head: {
          id: '00'
        }
      }
    };
  };

  beforeEach(module("arethusa.core", function($provide) {
    $provide.value('configurator', mockConfigurator);
  }));

  var state;
  beforeEach(inject(function(_state_) {
    state = _state_;
    state.tokens = createTokens();
  }));

  describe("this.countTotalTokens", function() {
    it('counts the total number of tokens, exposed through this.totalTokens', function() {
      state.countTotalTokens();
      expect(state.totalTokens).toEqual(4);
    });
  });

  describe('this.countTokens', function() {
    it('returns a count of tokens for which the given function is true', function() {
      var fn = function(token) {
        return token.string == "cano";
      };
      expect(state.countTokens(fn)).toEqual(1);
    });
  });

  describe('this.getToken', function() {
    it('returns a token by id', function() {
      var res = state.tokens['01'];
      expect(state.getToken('01')).toBe(res);
    });
  });

  describe('selection', function() {
    it('selects the clicked token', function() {
      state.selectToken('03', 'click');

      expect(state.selectedTokens).toEqual({'03': 'click'});
    });

    it('deselects all tokens after head change', function() {
      state.selectToken('01', 'click');
      state.selectToken('03', 'click');

      expect(state.selectedTokens).toEqual({});
    });

    it('selects multiple tokens with ctrl', function() {
      state.selectToken('01', 'ctrl-click');
      state.selectToken('03', 'ctrl-click');

      expect(state.selectedTokens).toEqual({'01': 'ctrl-click', '03': 'ctrl-click'});
    });
  });

  /* Default tree:
   *     04:cano
   *        |
   *     03:-que
   *      /   \
   *  01:Arma 02:virum
   */
  describe('this.handleChangeHead', function() {
    /*
     *     04:cano
     *     /    \
     * 03:-que 01:Arma
     *     |
     *  02:virum
     */
    it('parents a leaf node to the root', function() {
      state.selectToken('01', 'click');

      state.selectToken('04', 'click');

      expect(state.getToken('01').head.id).toBe('04');
    });

    /*
     *     04:cano
     *        |
     *     03:-que
     *        |
     *    02:virum
     *        |
     *     01:Arma
     */
    it('parents a leaf node to another leaf node', function() {
      state.selectToken('01', 'click');

      state.selectToken('02', 'click');

      expect(state.getToken('01').head.id).toBe('02');
    });

    /*
     *     04:cano
     *        |
     *     01:Arma
     *        |
     *     03:-que
     *        |
     *    02:virum
     */
    it('parents an inner node to a leaf node', function() {
      state.selectToken('03', 'click');

      state.selectToken('01', 'click');

      expect(state.getToken('03').head.id).toBe('01');
      expect(state.getToken('02').head.id).toBe('03');
      expect(state.getToken('01').head.id).toBe('04');
    });

    describe('with multiple selection', function() {
      /*
       *         04:cano
       *      /     |     \
       * 01:Arma 02:virum 03:-que
       */
      it('parents two leaf nodes to an inner node', function() {
        state.selectToken('01', 'click');
        state.selectToken('02', 'ctrl-click');

        state.selectToken('04', 'click');

        expect(state.getToken('01').head.id).toBe('04');
        expect(state.getToken('02').head.id).toBe('04');
        expect(state.getToken('03').head.id).toBe('04');
      });

      /*
       *     04:cano
       *        |
       *     03:-que
       *        |
       *    02:virum
       *        |
       *     01:Arma
       */
      it('ignores node in multiselection if it becomes head', function() {
        state.selectToken('01', 'click');
        state.selectToken('02', 'ctrl-click');

        state.selectToken('02', 'click');

        expect(state.getToken('01').head.id).toBe('02');
        expect(state.getToken('02').head.id).toBe('03');
      });
    });
  });

  describe('this.headsFor', function() {
    it('returns an empty array for the root', function() {
      expect(state.headsFor('00')).toEqual([]);
    });

    it('returns an array of head ids', function() {
      expect(state.headsFor('01')).toEqual(['03', '04', '00']);
    });
  });
});
<commit_msg>Add spec for state.asString()<commit_after>"use strict";

describe("state", function() {
  var mockConfigurator = {
    configurationFor: function(name) {
      return {};
    },
    getRetrievers: function(name) {
      return {};
    }
  };

  var createTokens = function() {
    return {
      '01': {
        id: '01',
        string: 'Arma',
        head: {
          id: '03'
        }
      },
      '02': {
        id: '02',
        string: 'virum',
        head: {
          id: '03',
        }
      },
      '03': {
        id: '03',
        string: '-que',
        head: {
          id: '04'
        }
      },
      '04': {
        id: '04',
        string: 'cano',
        head: {
          id: '00'
        }
      }
    };
  };

  beforeEach(module("arethusa.core", function($provide) {
    $provide.value('configurator', mockConfigurator);
  }));

  var state;
  beforeEach(inject(function(_state_) {
    state = _state_;
    state.tokens = createTokens();
  }));

  describe("this.countTotalTokens", function() {
    it('counts the total number of tokens, exposed through this.totalTokens', function() {
      state.countTotalTokens();
      expect(state.totalTokens).toEqual(4);
    });
  });

  describe('this.countTokens', function() {
    it('returns a count of tokens for which the given function is true', function() {
      var fn = function(token) {
        return token.string == "cano";
      };
      expect(state.countTokens(fn)).toEqual(1);
    });
  });

  describe('this.getToken', function() {
    it('returns a token by id', function() {
      var res = state.tokens['01'];
      expect(state.getToken('01')).toBe(res);
    });
  });

  describe('this.asString', function() {
    it('returns the string of a token identified by id', function() {
      expect(state.asString('01')).toEqual('Arma');
    });
  });

  describe('selection', function() {
    it('selects the clicked token', function() {
      state.selectToken('03', 'click');

      expect(state.selectedTokens).toEqual({'03': 'click'});
    });

    it('deselects all tokens after head change', function() {
      state.selectToken('01', 'click');
      state.selectToken('03', 'click');

      expect(state.selectedTokens).toEqual({});
    });

    it('selects multiple tokens with ctrl', function() {
      state.selectToken('01', 'ctrl-click');
      state.selectToken('03', 'ctrl-click');

      expect(state.selectedTokens).toEqual({'01': 'ctrl-click', '03': 'ctrl-click'});
    });
  });

  /* Default tree:
   *     04:cano
   *        |
   *     03:-que
   *      /   \
   *  01:Arma 02:virum
   */
  describe('this.handleChangeHead', function() {
    /*
     *     04:cano
     *     /    \
     * 03:-que 01:Arma
     *     |
     *  02:virum
     */
    it('parents a leaf node to the root', function() {
      state.selectToken('01', 'click');

      state.selectToken('04', 'click');

      expect(state.getToken('01').head.id).toBe('04');
    });

    /*
     *     04:cano
     *        |
     *     03:-que
     *        |
     *    02:virum
     *        |
     *     01:Arma
     */
    it('parents a leaf node to another leaf node', function() {
      state.selectToken('01', 'click');

      state.selectToken('02', 'click');

      expect(state.getToken('01').head.id).toBe('02');
    });

    /*
     *     04:cano
     *        |
     *     01:Arma
     *        |
     *     03:-que
     *        |
     *    02:virum
     */
    it('parents an inner node to a leaf node', function() {
      state.selectToken('03', 'click');

      state.selectToken('01', 'click');

      expect(state.getToken('03').head.id).toBe('01');
      expect(state.getToken('02').head.id).toBe('03');
      expect(state.getToken('01').head.id).toBe('04');
    });

    describe('with multiple selection', function() {
      /*
       *         04:cano
       *      /     |     \
       * 01:Arma 02:virum 03:-que
       */
      it('parents two leaf nodes to an inner node', function() {
        state.selectToken('01', 'click');
        state.selectToken('02', 'ctrl-click');

        state.selectToken('04', 'click');

        expect(state.getToken('01').head.id).toBe('04');
        expect(state.getToken('02').head.id).toBe('04');
        expect(state.getToken('03').head.id).toBe('04');
      });

      /*
       *     04:cano
       *        |
       *     03:-que
       *        |
       *    02:virum
       *        |
       *     01:Arma
       */
      it('ignores node in multiselection if it becomes head', function() {
        state.selectToken('01', 'click');
        state.selectToken('02', 'ctrl-click');

        state.selectToken('02', 'click');

        expect(state.getToken('01').head.id).toBe('02');
        expect(state.getToken('02').head.id).toBe('03');
      });
    });
  });

  describe('this.headsFor', function() {
    it('returns an empty array for the root', function() {
      expect(state.headsFor('00')).toEqual([]);
    });

    it('returns an array of head ids', function() {
      expect(state.headsFor('01')).toEqual(['03', '04', '00']);
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

var spawn = require('cross-spawn'),
    fs = require('fs-extra'),
    path = require('path'),
    glob = require('glob'),
    config = require('./config'),
    utils = require('./utils'),
    logger = require('./logger'),
    deps = require('./deps'),
    archiver = require('archiver'),
    moment = require('moment');

var webpack = require('webpack');

class Task {

    /**
     * init config
     * @method before
     * @param  {Object} args [description]
     * @return {[type]}      [description]
     */
    before(args) {
        var conf = config.init(args);
        return this.checkArgs(args).then(() => {
            if (conf.vue) {
                return this.checkDeps('vue');
            }
        }).then(() => {
            if (conf.es7) {
                return this.checkDeps('es7');
            }
        }).then(() => {
            if (conf.react) {
                return this.checkDeps('react');
            }
        });
    }

    /**
     * check args and install missing modules or dependencies
     * @method checkArgs
     * @param  {Object} args : process arguments
     * @return {Promise}
     */
    checkArgs(args) {
        if (args.mock) {
            return new Promise((resolve, reject) => {
                var mockPath = path.join(process.cwd(), 'mock', 'mock.js');
                fs.stat(mockPath, (err) => {
                    if (err) {
                        logger.error(`can't find ${mockPath}, have you ever ` + 'init mock module'.to
                            .bold.red.color + ' ? ');
                        logger.error('try to use ' + 'kil init -m'.to.bold.red.color +
                            ' to fix this issue. ');
                        reject(err);
                    }
                    resolve();
                });
            });
        }

        return Promise.resolve();
    }

    /**
     * init folders
     * @method initFolder
     * @param  {List}    folders
     * @return {[type]}                  [description]
     */
    initFolder(folders) {
        var list = [];
        folders.forEach((folder) => {
            list.push(new Promise((resolve, reject) => {
                fs.stat(folder, (err, status) => {
                    if (err) {
                        logger.info(` create folder ${folder} `);
                        fs.mkdirs(folder, () => {
                            resolve();
                        });
                    } else {
                        logger.info(` folder ${folder} already exists! `);
                        resolve();
                    }
                });
            }));
        });

        return Promise.all(list);
    }

    /**
     * init files
     * @method initFile
     * @param  {List}  files         [description]
     * @return {Promise}                [description]
     */
    initFile(files) {
        var list = [];
        files.forEach((file) => {
            list.push(new Promise((resolve, reject) => {
                fs.stat(file, (err, stats) => {
                    if (err) {
                        var origin = path.join(__dirname, 'default', file);
                        var dest = path.join(process.cwd(), file);
                        fs.copy(origin, dest, () => {
                            logger.info(` create file ${dest} `);
                            resolve();
                        });
                    } else {
                        logger.info(` file ${file} already exists! `);
                        resolve();
                    }
                });
            }));
        });

        return Promise.all(list);
    }

    initMock() {
        logger.info(' start init mock module ');
        var folders = ['mock'];
        var files = ['mock/mock.js'];

        return this.initModule(folders, files).then(() => {
            return this.checkDeps('mock');
        }).then(() => {
            logger.info(' init mock module successfully! ');
        });
    }

    initTest() {
        logger.info(' start init test module ');
        var folders = ['test/mocha', 'test/phantom'];
        var files = ['test/karma.conf.js', 'test/mocha/index.test.js', 'test/phantom/index.test.js'];

        return this.initModule(folders, files).then(() => {
            return this.checkDeps('test');
        }).then(() => {
            logger.info(' init test module successfully! ');
        });
    }

    initProj() {
        logger.info(' start init project ');
        var folders = ['js'];
        var files = ['pack.default.js', 'index.html', 'index.js', 'js/main.js'];

        return this.initModule(folders, files).then(() => {
            logger.info(' init project successfully. ');
        });
    }

    /**
     * init module: create folders and files
     * @method initModule
     * @param  {List}   folders [description]
     * @param  {List}   files   [description]
     * @return {[type]}           [description]
     */
    initModule(folders, files) {
        return this.initFolder(folders).then(() => {
            return this.initFile(files);
        });
    }

    /**
     * check modules and install missing dependencies
     * @method checkDeps
     * @param  {String}  conf : keys in deps
     * @return {[type]}         [description]
     */
    checkDeps(conf) {
        var projectJson = path.join(process.cwd(), 'package.json');
        return new Promise((resolve, reject) => {
            fs.readJson(projectJson, (err, pack) => {
                if (err) {
                    logger.error(`can't read project json file ${projectJson}`);
                    reject(err);
                }
                resolve(pack);
            });
        }).then((pack) => {
            var checklist = [];
            switch (conf) {
                case 'mock':
                case 'test':
                case 'vue':
                case 'es7':
                case 'react':
                    for (let key in deps[conf]) {
                        pack.dependencies[key] = deps[conf][key];
                        checklist.push(new Promise((resolve, reject) => {
                            try {
                                require(path.join(process.cwd(), 'node_modules',
                                    key));
                                resolve();
                            } catch (err) {
                                logger.info(` module ${key} not found! `);
                                reject();
                            }
                        }));
                    }
                    break;
                default:
                    break;
            }

            return Promise.all(checklist).then(null, () => {
                return this.installDependencies(pack);
            });
        });
    }

    /**
     * install dependencies accoring to package.json
     * @method installDependencies
     * @param  {JSON}            pack :
     * @return {Promise}                 [description]
     */
    installDependencies(pack) {
        return new Promise((resolve, reject) => {
            fs.writeJson(path.join(process.cwd(), 'package.json'), pack, (err) => {
                if (err) {
                    logger.error(err);
                    reject(err);
                } else {
                    logger.info(' check and install module dependencies. ');
                    spawn('npm', ['install'], {
                        stdio: 'inherit'
                    }).on('close', (code) => {
                        resolve();
                    });
                }
            });
        });
    }

    /**
     * init config, check dependencies and exec task
     * @method {EXEC} exec
     * @param  {Object} args :
     * @param  {String} task : task name
     */
    exec(args, task) {
        let promise = this.before(args);
        promise.then(() => {
            console.log(456);
            logger.debug(`exec task ${task} with args:`);
            logger.debug(args);
            this[task](args);
        }, () => {
            console.log(123);
        });
    }

    /**
     * init a new project with mock and test module (default is not initilized)
     * @method {TASK} init
     * @param  {Object} args {test: false, mock: false}
     * @return {[type]}      [description]
     */
    init(args) {
        var mock = !!args.mock;
        var test = !!args.test;

        new Promise((resolve, reject) => {
            var packageJson = path.join(process.cwd(), 'package.json');
            fs.stat(packageJson, (err) => {
                if (err) {
                    logger.info(' package.json is not found, npm init');
                    spawn('npm', ['init'], {
                        stdio: 'inherit'
                    }).on('close', (code) => {
                        logger.info(
                            ' add key kil in package.json for system configuration. ');
                        var pack = require(packageJson);
                        pack.kil = require('./default/package.default.js');
                        fs.writeJson('package.json', pack, (err) => {
                            if (err) {
                                reject(err);
                            } else {
                                resolve();
                            }
                        });
                    });
                } else {
                    resolve();
                }
            });
        }).then(() => {
            return this.initProj();
        }).then(() => {
            if (mock) {
                return this.initMock();
            }
        }).then(() => {
            if (test) {
                return this.initTest();
            }
        }).catch((err) => {
            if (err) {
                console.log(err);
            }
        });
    }

    /**
     * load webpack config and start webpack dev server
     * @method dev
     * @param  {Object} args : {mock: true, port: 9000}
     */
    dev(args) {
        var pack_config = utils.loadWebpackCfg('dev', args);
        var compiler = webpack(pack_config);
        var WebpackDevServer = require('webpack-dev-server');
        var serverCfg = {
            hot: true,
            watchOptions: {
                poll: 1000
            },
            stats: {
                colors: true
            }
        };

        if (pack_config.devServer && pack_config.devServer.proxy) {
            serverCfg.proxy = pack_config.devServer.proxy;
        }
        if (config.getHtml5Mode()) {
            serverCfg.historyApiFallback = true;
        }

        logger.debug('webpack dev server start with config: ');
        logger.debug(serverCfg);

        new WebpackDevServer(compiler, serverCfg).listen(config.getPort(), '127.0.0.1', (err) => {
            if (err) {
                logger.error(err);
                process.exit(1);
            }

            logger.info('----------------------------------');
            logger.info(`Server listening at localhost:${config.getPort()}`);
            logger.info('----------------------------------');
        });
    }

    /**
     * do unit tests with mocha and endless tests with phantom
     * @method test
     * @param  {Object} args: {phantom: false, mocha: false}
     */
    test(args) {
        var mocha = !!args.mocha;
        var phantom = !!args.phantom;
        var server = !!args.server;

        var testPath = path.join(process.cwd(), 'test');
        fs.stat(testPath, (err) => {
            if (err) {
                logger.error(`can't find ${testPath}, have you ever ` + 'init test module'.to.bold.red.color +
                    ' ? ');
                logger.error('try to use ' + 'kil init -t'.to.bold.red.color + ' to fix this issue. ');

                process.exit(1);
            }

            if (mocha) {
                var karmaBin = path.join(__dirname, 'node_modules/karma/bin/karma');
                var karmaConfPath = path.join(process.cwd(), 'test/karma.conf.js');

                var bins;
                if (server) {
                    bins = ['start', karmaConfPath, '--detached'];
                } else {
                    bins = ['start', karmaConfPath, '--detached', '--single-run'];
                }

                spawn(karmaBin, bins, {
                    stdio: 'inherit'
                }).on('close', (code) => {
                    logger.info(`mocha test finished with code : ${code}`);
                });
            }

            if (phantom) {
                var phantomBin = path.join(__dirname, 'node_modules/phantomjs/bin/phantomjs');
                var phantomEntry = path.join(process.cwd(), 'test/phantom/index.test.js');
                spawn(phantomBin, [phantomEntry], {
                    stdio: 'inherit',
                    cwd: __dirname
                }).on('close', (code) => {
                    logger.info(`phontom test finished with code : ${code}`);
                });
            }
        });
    }

    /**
     * use webpack and build bundle
     * @method build
     * @param  {Object} args  [description]
     * @param  {Function} after : callback after build
     */
    build(args, after) {
        var pack_config = utils.loadWebpackCfg('release', args);

        logger.info('start build project... ');

        var compiler = webpack(pack_config);
        compiler.run((err, stats) => {
            if (err) {
                logger.error(err);

                process.exit(1);
            }

            var jsonStats = stats.toJson();
            if (jsonStats.errors.length > 0) {
                logger.error(jsonStats.errors);
            }
            if (jsonStats.warnings.length > 0) {
                logger.warn(jsonStats.warnings);
            }

            var conf = config.getConfig();
            if (conf.copy && conf.copy.length > 0) {
                conf.copy.forEach((key) => {
                    let files = glob.sync(key);
                    files.forEach((file) => {
                        fs.copySync(file, `dist/${file}`);
                    });
                });
            }

            logger.info('build successfully. ');

            if (after && typeof after === "function") {
                after(pack_config);
            }
        });
    }

    /**
     * use webpack and build bundle
     * @return {[type]} [description]
     */
    release(args) {
        this.build(args, (config) => {
            var releaseFile = './' + moment().format('YYYYMMDD-HHmmss') + '-release.zip';
            var output = fs.createWriteStream(releaseFile);
            output.on('close', function() {
                logger.info(`kil release successfully! see ${releaseFile}`);
            });

            var zipArchive = archiver('zip');
            zipArchive.pipe(output);
            zipArchive.bulk([{
                src: [path.relative(config.output.path, process.cwd()) + '/*'],
                cwd: process.cwd(),
                expand: true
            }]);
            zipArchive.finalize((err, bytes) => {
                if (err) {
                    throw err;
                }
            });
        });
    }
}

module.exports = new Task();
<commit_msg>fix bug<commit_after>'use strict';

var spawn = require('cross-spawn'),
    fs = require('fs-extra'),
    path = require('path'),
    glob = require('glob'),
    config = require('./config'),
    utils = require('./utils'),
    logger = require('./logger'),
    deps = require('./deps'),
    archiver = require('archiver'),
    moment = require('moment');

var webpack = require('webpack');

class Task {

    /**
     * init config
     * @method before
     * @param  {Object} args [description]
     * @return {[type]}      [description]
     */
    before(args) {
        var conf = config.init(args);
        return this.checkArgs(args).then(() => {
            if (conf.vue) {
                return this.checkDeps('vue');
            }
        }).then(() => {
            if (conf.es7) {
                return this.checkDeps('es7');
            }
        }).then(() => {
            if (conf.react) {
                return this.checkDeps('react');
            }
        });
    }

    /**
     * check args and install missing modules or dependencies
     * @method checkArgs
     * @param  {Object} args : process arguments
     * @return {Promise}
     */
    checkArgs(args) {
        if (args.mock) {
            return new Promise((resolve, reject) => {
                var mockPath = path.join(process.cwd(), 'mock', 'mock.js');
                fs.stat(mockPath, (err) => {
                    if (err) {
                        logger.error(`can't find ${mockPath}, have you ever ` + 'init mock module'.to
                            .bold.red.color + ' ? ');
                        logger.error('try to use ' + 'kil init -m'.to.bold.red.color +
                            ' to fix this issue. ');
                        reject(err);
                    }
                    resolve();
                });
            });
        }

        return Promise.resolve();
    }

    /**
     * init folders
     * @method initFolder
     * @param  {List}    folders
     * @return {[type]}                  [description]
     */
    initFolder(folders) {
        var list = [];
        folders.forEach((folder) => {
            list.push(new Promise((resolve, reject) => {
                fs.stat(folder, (err, status) => {
                    if (err) {
                        logger.info(` create folder ${folder} `);
                        fs.mkdirs(folder, () => {
                            resolve();
                        });
                    } else {
                        logger.info(` folder ${folder} already exists! `);
                        resolve();
                    }
                });
            }));
        });

        return Promise.all(list);
    }

    /**
     * init files
     * @method initFile
     * @param  {List}  files         [description]
     * @return {Promise}                [description]
     */
    initFile(files) {
        var list = [];
        files.forEach((file) => {
            list.push(new Promise((resolve, reject) => {
                fs.stat(file, (err, stats) => {
                    if (err) {
                        var origin = path.join(__dirname, 'default', file);
                        var dest = path.join(process.cwd(), file);
                        fs.copy(origin, dest, () => {
                            logger.info(` create file ${dest} `);
                            resolve();
                        });
                    } else {
                        logger.info(` file ${file} already exists! `);
                        resolve();
                    }
                });
            }));
        });

        return Promise.all(list);
    }

    initMock() {
        logger.info(' start init mock module ');
        var folders = ['mock'];
        var files = ['mock/mock.js'];

        return this.initModule(folders, files).then(() => {
            return this.checkDeps('mock');
        }).then(() => {
            logger.info(' init mock module successfully! ');
        });
    }

    initTest() {
        logger.info(' start init test module ');
        var folders = ['test/mocha', 'test/phantom'];
        var files = ['test/karma.conf.js', 'test/mocha/index.test.js', 'test/phantom/index.test.js'];

        return this.initModule(folders, files).then(() => {
            return this.checkDeps('test');
        }).then(() => {
            logger.info(' init test module successfully! ');
        });
    }

    initProj() {
        logger.info(' start init project ');
        var folders = ['js'];
        var files = ['pack.default.js', 'index.html', 'index.js', 'js/main.js'];

        return this.initModule(folders, files).then(() => {
            logger.info(' init project successfully. ');
        });
    }

    /**
     * init module: create folders and files
     * @method initModule
     * @param  {List}   folders [description]
     * @param  {List}   files   [description]
     * @return {[type]}           [description]
     */
    initModule(folders, files) {
        return this.initFolder(folders).then(() => {
            return this.initFile(files);
        });
    }

    /**
     * check modules and install missing dependencies
     * @method checkDeps
     * @param  {String}  conf : keys in deps
     * @return {[type]}         [description]
     */
    checkDeps(conf) {
        var projectJson = path.join(process.cwd(), 'package.json');
        return new Promise((resolve, reject) => {
            fs.readJson(projectJson, (err, pack) => {
                if (err) {
                    logger.error(`can't read project json file ${projectJson}`);
                    reject(err);
                }
                resolve(pack);
            });
        }).then((pack) => {
            var checklist = [];
            switch (conf) {
                case 'mock':
                case 'test':
                case 'vue':
                case 'es7':
                case 'react':
                    for (let key in deps[conf]) {
                        pack.dependencies[key] = deps[conf][key];
                        checklist.push(new Promise((resolve, reject) => {
                            try {
                                require(path.join(process.cwd(), 'node_modules',
                                    key));
                                resolve();
                            } catch (err) {
                                logger.info(` module ${key} not found! `);
                                reject();
                            }
                        }));
                    }
                    break;
                default:
                    break;
            }

            return Promise.all(checklist).then(null, () => {
                return this.installDependencies(pack);
            });
        });
    }

    /**
     * install dependencies accoring to package.json
     * @method installDependencies
     * @param  {JSON}            pack :
     * @return {Promise}                 [description]
     */
    installDependencies(pack) {
        return new Promise((resolve, reject) => {
            fs.writeJson(path.join(process.cwd(), 'package.json'), pack, (err) => {
                if (err) {
                    logger.error(err);
                    reject(err);
                } else {
                    logger.info(' check and install module dependencies. ');
                    spawn('npm', ['install'], {
                        stdio: 'inherit'
                    }).on('close', (code) => {
                        resolve();
                    });
                }
            });
        });
    }

    /**
     * init config, check dependencies and exec task
     * @method {EXEC} exec
     * @param  {Object} args :
     * @param  {String} task : task name
     */
    exec(args, task) {
        let promise = this.before(args);
        promise.then(() => {
            logger.debug(`exec task ${task} with args:`);
            logger.debug(args);
            this[task](args);
        }, () => {
            logger.error(`exec task ${task} failed`);
        });
    }

    /**
     * init a new project with mock and test module (default is not initilized)
     * @method {TASK} init
     * @param  {Object} args {test: false, mock: false}
     * @return {[type]}      [description]
     */
    init(args) {
        var mock = !!args.mock;
        var test = !!args.test;

        new Promise((resolve, reject) => {
            var packageJson = path.join(process.cwd(), 'package.json');
            fs.stat(packageJson, (err) => {
                if (err) {
                    logger.info(' package.json is not found, npm init');
                    spawn('npm', ['init'], {
                        stdio: 'inherit'
                    }).on('close', (code) => {
                        logger.info(
                            ' add key kil in package.json for system configuration. ');
                        var pack = require(packageJson);
                        pack.kil = require('./default/package.default.js');
                        fs.writeJson('package.json', pack, (err) => {
                            if (err) {
                                reject(err);
                            } else {
                                resolve();
                            }
                        });
                    });
                } else {
                    resolve();
                }
            });
        }).then(() => {
            return this.initProj();
        }).then(() => {
            if (mock) {
                return this.initMock();
            }
        }).then(() => {
            if (test) {
                return this.initTest();
            }
        }).catch((err) => {
            if (err) {
                console.log(err);
            }
        });
    }

    /**
     * load webpack config and start webpack dev server
     * @method dev
     * @param  {Object} args : {mock: true, port: 9000}
     */
    dev(args) {
        var pack_config = utils.loadWebpackCfg('dev', args);
        var compiler = webpack(pack_config);
        var WebpackDevServer = require('webpack-dev-server');
        var serverCfg = {
            hot: true,
            watchOptions: {
                poll: 1000
            },
            stats: {
                colors: true
            }
        };

        if (pack_config.devServer && pack_config.devServer.proxy) {
            serverCfg.proxy = pack_config.devServer.proxy;
        }
        if (config.getHtml5Mode()) {
            serverCfg.historyApiFallback = true;
        }

        logger.debug('webpack dev server start with config: ');
        logger.debug(serverCfg);

        new WebpackDevServer(compiler, serverCfg).listen(config.getPort(), '127.0.0.1', (err) => {
            if (err) {
                logger.error(err);
                process.exit(1);
            }

            logger.info('----------------------------------');
            logger.info(`Server listening at localhost:${config.getPort()}`);
            logger.info('----------------------------------');
        });
    }

    /**
     * do unit tests with mocha and endless tests with phantom
     * @method test
     * @param  {Object} args: {phantom: false, mocha: false}
     */
    test(args) {
        var mocha = !!args.mocha;
        var phantom = !!args.phantom;
        var server = !!args.server;

        var testPath = path.join(process.cwd(), 'test');
        fs.stat(testPath, (err) => {
            if (err) {
                logger.error(`can't find ${testPath}, have you ever ` + 'init test module'.to.bold.red.color +
                    ' ? ');
                logger.error('try to use ' + 'kil init -t'.to.bold.red.color + ' to fix this issue. ');

                process.exit(1);
            }

            if (mocha) {
                var karmaBin = path.join(__dirname, 'node_modules/karma/bin/karma');
                var karmaConfPath = path.join(process.cwd(), 'test/karma.conf.js');

                var bins;
                if (server) {
                    bins = ['start', karmaConfPath, '--detached'];
                } else {
                    bins = ['start', karmaConfPath, '--detached', '--single-run'];
                }

                spawn(karmaBin, bins, {
                    stdio: 'inherit'
                }).on('close', (code) => {
                    logger.info(`mocha test finished with code : ${code}`);
                });
            }

            if (phantom) {
                var phantomBin = path.join(__dirname, 'node_modules/phantomjs/bin/phantomjs');
                var phantomEntry = path.join(process.cwd(), 'test/phantom/index.test.js');
                spawn(phantomBin, [phantomEntry], {
                    stdio: 'inherit',
                    cwd: __dirname
                }).on('close', (code) => {
                    logger.info(`phontom test finished with code : ${code}`);
                });
            }
        });
    }

    /**
     * use webpack and build bundle
     * @method build
     * @param  {Object} args  [description]
     * @param  {Function} after : callback after build
     */
    build(args, after) {
        var pack_config = utils.loadWebpackCfg('release', args);

        logger.info('start build project... ');

        var compiler = webpack(pack_config);
        compiler.run((err, stats) => {
            if (err) {
                logger.error(err);

                process.exit(1);
            }

            var jsonStats = stats.toJson();
            if (jsonStats.errors.length > 0) {
                logger.error(jsonStats.errors);
            }
            if (jsonStats.warnings.length > 0) {
                logger.warn(jsonStats.warnings);
            }

            var conf = config.getConfig();
            if (conf.copy && conf.copy.length > 0) {
                conf.copy.forEach((key) => {
                    let files = glob.sync(key);
                    files.forEach((file) => {
                        fs.copySync(file, `dist/${file}`);
                    });
                });
            }

            logger.info('build successfully. ');

            if (after && typeof after === "function") {
                after(pack_config);
            }
        });
    }

    /**
     * use webpack and build bundle
     * @return {[type]} [description]
     */
    release(args) {
        this.build(args, (config) => {
            var releaseFile = './' + moment().format('YYYYMMDD-HHmmss') + '-release.zip';
            var output = fs.createWriteStream(releaseFile);
            output.on('close', function() {
                logger.info(`kil release successfully! see ${releaseFile}`);
            });

            var zipArchive = archiver('zip');
            zipArchive.pipe(output);
            zipArchive.bulk([{
                src: [path.relative(config.output.path, process.cwd()) + '/*'],
                cwd: process.cwd(),
                expand: true
            }]);
            zipArchive.finalize((err, bytes) => {
                if (err) {
                    throw err;
                }
            });
        });
    }
}

module.exports = new Task();
<|endoftext|>