<commit_before>const { mergeDefault } = require('../util/util');
const quotes = ['"', "'", '“”', '‘’'];

/**
 * A class to handle argument collection and parameter resolution
 */
class TextPrompt {

	/**
	 * @typedef {Object} TextPromptOptions
	 * @property {number} [promptLimit=Infinity] The number of re-prompts before this TextPrompt gives up
	 * @property {number} [promptTime=30000] The time-limit for re-prompting
	 * @property {boolean} [quotedStringSupport=false] Whether this prompt should respect quoted strings
	 * @memberof TextPrompt
	 */

	/**
	 * @since 0.5.0
	 * @param {KlasaMessage} msg The message this prompt is for
	 * @param {ParsedUsage} usage The usage for this prompt
	 * @param {TextPromptOptions} options The options of this prompt
	 */
	constructor(msg, usage, options) {
		options = mergeDefault(msg.client.options.customPromptDefaults, options);

		/**
		 * The client this TextPrompt was created with
		 * @since 0.5.0
		 * @name TextPrompt#client
		 * @type {KlasaClient}
		 * @readonly
		 */
		Object.defineProperty(this, 'client', { value: msg.client });

		/**
		 * The message this prompt is for
		 * @since 0.5.0
		 * @type {KlasaMessage}
		 */
		this.message = msg;

		/**
		 * The usage for this prompt
		 * @since 0.5.0
		 * @type {ParsedUsage|CommandUsage}
		 */
		this.usage = usage;

		/**
		 * If the command reprompted for missing args
		 * @since 0.0.1
		 * @type {boolean}
		 */
		this.reprompted = false;

		/**
		 * The flag arguments resolved by this class
		 * @since 0.5.0
		 * @type {Object}
		 */
		this.flags = {};

		/**
		 * The string arguments derived from the usageDelim of the command
		 * @since 0.0.1
		 * @type {string[]}
		 */
		this.args = [];

		/**
		 * The parameters resolved by this class
		 * @since 0.0.1
		 * @type {any[]}
		 */
		this.params = [];

		/**
		 * The time-limit for re-prompting
		 * @since 0.5.0
		 * @type {number}
		 */
		this.promptTime = options.promptTime;

		/**
		 * The number of re-prompts before this TextPrompt gives up
		 * @since 0.5.0
		 * @type {number}
		 */
		this.promptLimit = options.promptLimit;

		/**
		 * Whether this prompt should respect quoted strings
		 * @since 0.5.0
		 * @type {boolean}
		 */
		this.quotedStringSupport = options.quotedStringSupport;

		/**
		 * Whether the current usage is a repeating arg
		 * @since 0.0.1
		 * @type {boolean}
		 * @private
		 */
		this._repeat = false;

		/**
		 * Whether the current usage is required
		 * @since 0.0.1
		 * @type {number}
		 * @private
		 */
		this._required = 0;

		/**
		 * How many time this class has reprompted
		 * @since 0.0.1
		 * @type {boolean}
		 * @private
		 */
		this._prompted = 0;

		/**
		 * A cache of the current usage while validating
		 * @since 0.0.1
		 * @type {Tag}
		 * @private
		 */
		this._currentUsage = {};
	}

	/**
	 * Runs the custom prompt.
	 * @since 0.5.0
	 * @param {string} prompt The message to initially prompt with
	 * @returns {Promise<any[]>} The parameters resolved
	 */
	async run(prompt) {
		const message = await this.message.prompt(prompt, this.promptTime);
		this._setup(message.content);
		return this.validateArgs();
	}

	/**
	 * Collects missing required arguments.
	 * @since 0.5.0
	 * @param {string} prompt The reprompt error
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async reprompt(prompt) {
		this._prompted++;
		if (this.typing) this.message.channel.stopTyping();
		const message = await this.message.prompt(
			this.message.language.get('MONITOR_COMMAND_HANDLER_REPROMPT', `<@!${this.message.author.id}>`, prompt, this.promptTime / 1000),
			this.promptTime
		);

		if (message.content.toLowerCase() === 'abort') throw this.message.language.get('MONITOR_COMMAND_HANDLER_ABORTED');

		if (this.typing) this.message.channel.startTyping();
		this.args[this.args.lastIndexOf(null)] = message.content;
		this.reprompted = true;

		if (this.usage.parsedUsage[this.params.length + 1] && this.usage.parsedUsage[this.params.length + 1].type === 'repeat') {
			return this.repeatingPrompt(prompt);
		}

		return this.validateArgs();
	}

	/**
	 * Collects repeating arguments.
	 * @since 0.5.0
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async repeatingPrompt() {
		if (this.typing) this.message.channel.stopTyping();
		let message;

		try {
			message = await this.message.prompt(
				this.message.language.get('MONITOR_COMMAND_HANDLER_REPEATING_REPROMPT', `<@!${this.message.author.id}>`, this._currentUsage.possibles[0].name, this.promptTime / 1000),
				this.promptTime
			);
		} catch (err) {
			return this.validateArgs();
		}

		if (message.content.toLowerCase() === 'cancel') return this.validateArgs();

		if (this.typing) this.message.channel.startTyping();
		this.args.push(message.content);
		this.reprompted = true;

		return this.repeatingPrompt();
	}

	/**
	 * Validates and resolves args into parameters
	 * @since 0.0.1
	 * @returns {Promise<any[]>} The resolved parameters
	 * @private
	 */
	async validateArgs() {
		if (this.params.length >= this.usage.parsedUsage.length && this.params.length >= this.args.length) {
			return this.finalize();
		} else if (this.usage.parsedUsage[this.params.length]) {
			this._currentUsage = this.usage.parsedUsage[this.params.length];
			this._required = this._currentUsage.required;
		} else if (this._currentUsage.repeat) {
			this._required = 0;
			this._repeat = true;
		} else {
			return this.finalize();
		}

		if (this._currentUsage.possibles.length !== 1) return this.multiPossibles(0);

		const possible = this._currentUsage.possibles[0];
		const custom = this.usage.customResolvers[possible.type];

		if (possible.name in this.flags) this.args.splice(this.params.length, 0, this.flags[possible.name]);
		if (!this.client.argResolver[possible.type] && !custom) {
			this.client.emit('warn', `Unknown Argument Type encountered: ${possible.type}`);
			return this.pushParam(undefined);
		}

		try {
			const res = await this.client.argResolver[custom ? 'custom' : possible.type](this.args[this.params.length], possible, this.message, custom);
			return this.pushParam(res);
		} catch (err) {
			if (!this._required) {
				this.args.splice(this.params.length, 0, undefined);
				return this.pushParam(undefined);
			}

			const { response } = this._currentUsage;
			const error = typeof response === 'function' ? response(this.message, possible) : response;

			if (this._required === 1) return this.handleError(response || err);
			return this.handleError(error || (this.args[this.params.length] === undefined ?
				this.message.language.get('COMMANDMESSAGE_MISSING_REQUIRED', possible.name) :
				err)
			);
		}
	}

	/**
	 * Validates and resolves args into parameters, when multiple types of usage is defined
	 * @since 0.0.1
	 * @param {number} index The id of the possible usage currently being checked
	 * @returns {Promise<any[]>} The resolved parameters
	 * @private
	 */
	async multiPossibles(index) {
		const possible = this._currentUsage.possibles[index];
		const custom = this.usage.customResolvers[possible.type];

		if (possible.name in this.flags) this.args.splice(this.params.length, 0, this.flags[possible.name]);
		if (!this.client.argResolver[possible.type] && !custom) {
			this.client.emit('warn', `Unknown Argument Type encountered: ${possible.type}`);
			return this.multiPossibles(++index);
		}

		try {
			const res = await this.client.argResolver[custom ? 'custom' : possible.type](this.args[this.params.length], possible, this.message, custom);
			return this.pushParam(res);
		} catch (err) {
			if (index < this._currentUsage.possibles.length - 1) return this.multiPossibles(++index);
			if (!this._required) {
				this.args.splice(this.params.length, 0, undefined);
				return this.pushParam(undefined);
			}

			const { response } = this._currentUsage;
			const error = typeof response === 'function' ? response(this.message, possible) : response;

			if (this._required === 1) return this.handleError(response || err);
			return this.handleError(error || this.message.language.get('COMMANDMESSAGE_NOMATCH', this._currentUsage.possibles.map(poss => poss.name).join(', ')));
		}
	}

	/**
	 * Pushes a parameter into this.params, and resets the re-prompt count.
	 * @since 0.5.0
	 * @param {any} param The resolved parameter
	 * @returns {any[]}
	 * @private
	 */
	pushParam(param) {
		this._prompted = 0;
		this.params.push(param);
		return this.validateArgs();
	}

	/**
	 * Decides if the prompter should reprompt or throw the error found while validating.
	 * @since 0.5.0
	 * @param {string} err The error found while validating
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async handleError(err) {
		this.args.splice(this.params.length, 1, null);
		if (this.promptLimit && this._prompted < this.promptLimit) return this.reprompt(err);
		throw err;
	}

	/**
	 * Finalizes parameters and arguments for this prompt.
	 * @since 0.5.0
	 * @returns {any[]}
	 * @private
	 */
	finalize() {
		for (let i = this.params.length - 1; i >= 0 && this.params[i] === undefined; i--) this.params.pop();
		for (let i = this.args.length - 1; i >= 0 && this.args[i] === undefined; i--) this.args.pop();
		return this.params;
	}

	/**
	 * Splits the original message string into arguments.
	 * @since 0.5.0
	 * @param {string} original The original message string
	 * @returns {void}
	 * @private
	 */
	_setup(original) {
		const { content, flags } = this.constructor.getFlags(original, this.usage.usageDelim);
		this.flags = flags;
		this.args = this.quotedStringSupport ?
			this.constructor.getQuotedStringArgs(content, this.usage.usageDelim).map(arg => arg.trim()) :
			this.constructor.getArgs(content, this.usage.usageDelim).map(arg => arg.trim());
	}

	/**
	 * Parses a message into string args
	 * @since 0.5.0
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {Object}
	 * @private
	 */
	static getFlags(content, delim) {
		const flags = {};
		content = content.replace(TextPrompt.flagRegex, (match, fl, ...quote) => {
			flags[fl] = (quote.slice(0, -2).find(el => el) || fl).replace(/\\/g, '');
			return '';
		}).replace(new RegExp(`(?:(\\s)\\s+|(${delim})(?:${delim})+)`, 'g'), '$1').trim();
		return { content, flags };
	}

	/**
	 * Parses a message into string args
	 * @since 0.0.1
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {string[]}
	 * @private
	 */
	static getArgs(content, delim) {
		const args = content.replace(new RegExp(`(${delim})(?:${delim})+`, 'g'), '$1').split(delim !== '' ? delim : undefined);
		return args.length === 1 && args[0] === '' ? [] : args;
	}

	/**
	 * Parses a message into string args taking into account quoted strings
	 * @since 0.0.1
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {string[]}
	 * @private
	 */
	static getQuotedStringArgs(content, delim) {
		if (!delim || delim === '') return [content];

		const args = [];

		for (let i = 0; i < content.length; i++) {
			let current = '';
			if (content.slice(i, i + delim.length) === delim) {
				i += delim.length - 1;
				continue;
			}
			const quote = quotes.find(qt => qt.includes(content[i]));
			if (quote) {
				const qts = quote.split('');
				while (i + 1 < content.length && (content[i] === '\\' || !qts.includes(content[i + 1]))) current += content[++i] !== '\\' ? content[i] : '';
				i++;
				args.push(current);
			} else {
				current += content[i];
				while (i + 1 < content.length && content.slice(i + 1, i + delim.length + 1) !== delim) current += content[++i];
				args.push(current);
			}
		}

		return args.length === 1 && args[0] === '' ? [] : args;
	}

}

/**
 * Regular Expression to match flags with quoted string support.
 * @since 0.5.0
 * @type {RegExp}
 * @static
 */
TextPrompt.flagRegex = new RegExp(`--([\\w-]{2,})(?:=(?:${quotes.map(qu => `[${qu}]((?:[^${qu}\\\\]|\\\\.)*)[${qu}]`).join('|')}|([\\w-]+)))?`, 'g');

module.exports = TextPrompt;
<commit_msg>custom response bugfixes<commit_after>const { mergeDefault } = require('../util/util');
const quotes = ['"', "'", '“”', '‘’'];

/**
 * A class to handle argument collection and parameter resolution
 */
class TextPrompt {

	/**
	 * @typedef {Object} TextPromptOptions
	 * @property {number} [promptLimit=Infinity] The number of re-prompts before this TextPrompt gives up
	 * @property {number} [promptTime=30000] The time-limit for re-prompting
	 * @property {boolean} [quotedStringSupport=false] Whether this prompt should respect quoted strings
	 * @memberof TextPrompt
	 */

	/**
	 * @since 0.5.0
	 * @param {KlasaMessage} msg The message this prompt is for
	 * @param {ParsedUsage} usage The usage for this prompt
	 * @param {TextPromptOptions} options The options of this prompt
	 */
	constructor(msg, usage, options) {
		options = mergeDefault(msg.client.options.customPromptDefaults, options);

		/**
		 * The client this TextPrompt was created with
		 * @since 0.5.0
		 * @name TextPrompt#client
		 * @type {KlasaClient}
		 * @readonly
		 */
		Object.defineProperty(this, 'client', { value: msg.client });

		/**
		 * The message this prompt is for
		 * @since 0.5.0
		 * @type {KlasaMessage}
		 */
		this.message = msg;

		/**
		 * The usage for this prompt
		 * @since 0.5.0
		 * @type {ParsedUsage|CommandUsage}
		 */
		this.usage = usage;

		/**
		 * If the command reprompted for missing args
		 * @since 0.0.1
		 * @type {boolean}
		 */
		this.reprompted = false;

		/**
		 * The flag arguments resolved by this class
		 * @since 0.5.0
		 * @type {Object}
		 */
		this.flags = {};

		/**
		 * The string arguments derived from the usageDelim of the command
		 * @since 0.0.1
		 * @type {string[]}
		 */
		this.args = [];

		/**
		 * The parameters resolved by this class
		 * @since 0.0.1
		 * @type {any[]}
		 */
		this.params = [];

		/**
		 * The time-limit for re-prompting
		 * @since 0.5.0
		 * @type {number}
		 */
		this.promptTime = options.promptTime;

		/**
		 * The number of re-prompts before this TextPrompt gives up
		 * @since 0.5.0
		 * @type {number}
		 */
		this.promptLimit = options.promptLimit;

		/**
		 * Whether this prompt should respect quoted strings
		 * @since 0.5.0
		 * @type {boolean}
		 */
		this.quotedStringSupport = options.quotedStringSupport;

		/**
		 * Whether the current usage is a repeating arg
		 * @since 0.0.1
		 * @type {boolean}
		 * @private
		 */
		this._repeat = false;

		/**
		 * Whether the current usage is required
		 * @since 0.0.1
		 * @type {number}
		 * @private
		 */
		this._required = 0;

		/**
		 * How many time this class has reprompted
		 * @since 0.0.1
		 * @type {boolean}
		 * @private
		 */
		this._prompted = 0;

		/**
		 * A cache of the current usage while validating
		 * @since 0.0.1
		 * @type {Tag}
		 * @private
		 */
		this._currentUsage = {};
	}

	/**
	 * Runs the custom prompt.
	 * @since 0.5.0
	 * @param {string} prompt The message to initially prompt with
	 * @returns {Promise<any[]>} The parameters resolved
	 */
	async run(prompt) {
		const message = await this.message.prompt(prompt, this.promptTime);
		this._setup(message.content);
		return this.validateArgs();
	}

	/**
	 * Collects missing required arguments.
	 * @since 0.5.0
	 * @param {string} prompt The reprompt error
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async reprompt(prompt) {
		this._prompted++;
		if (this.typing) this.message.channel.stopTyping();
		const message = await this.message.prompt(
			this.message.language.get('MONITOR_COMMAND_HANDLER_REPROMPT', `<@!${this.message.author.id}>`, prompt, this.promptTime / 1000),
			this.promptTime
		);

		if (message.content.toLowerCase() === 'abort') throw this.message.language.get('MONITOR_COMMAND_HANDLER_ABORTED');

		if (this.typing) this.message.channel.startTyping();
		this.args[this.args.lastIndexOf(null)] = message.content;
		this.reprompted = true;

		if (this.usage.parsedUsage[this.params.length + 1] && this.usage.parsedUsage[this.params.length + 1].type === 'repeat') {
			return this.repeatingPrompt(prompt);
		}

		return this.validateArgs();
	}

	/**
	 * Collects repeating arguments.
	 * @since 0.5.0
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async repeatingPrompt() {
		if (this.typing) this.message.channel.stopTyping();
		let message;

		try {
			message = await this.message.prompt(
				this.message.language.get('MONITOR_COMMAND_HANDLER_REPEATING_REPROMPT', `<@!${this.message.author.id}>`, this._currentUsage.possibles[0].name, this.promptTime / 1000),
				this.promptTime
			);
		} catch (err) {
			return this.validateArgs();
		}

		if (message.content.toLowerCase() === 'cancel') return this.validateArgs();

		if (this.typing) this.message.channel.startTyping();
		this.args.push(message.content);
		this.reprompted = true;

		return this.repeatingPrompt();
	}

	/**
	 * Validates and resolves args into parameters
	 * @since 0.0.1
	 * @returns {Promise<any[]>} The resolved parameters
	 * @private
	 */
	async validateArgs() {
		if (this.params.length >= this.usage.parsedUsage.length && this.params.length >= this.args.length) {
			return this.finalize();
		} else if (this.usage.parsedUsage[this.params.length]) {
			this._currentUsage = this.usage.parsedUsage[this.params.length];
			this._required = this._currentUsage.required;
		} else if (this._currentUsage.repeat) {
			this._required = 0;
			this._repeat = true;
		} else {
			return this.finalize();
		}

		if (this._currentUsage.possibles.length !== 1) return this.multiPossibles(0);

		const possible = this._currentUsage.possibles[0];
		const custom = this.usage.customResolvers[possible.type];

		if (possible.name in this.flags) this.args.splice(this.params.length, 0, this.flags[possible.name]);
		if (!this.client.argResolver[possible.type] && !custom) {
			this.client.emit('warn', `Unknown Argument Type encountered: ${possible.type}`);
			return this.pushParam(undefined);
		}

		try {
			const res = await this.client.argResolver[custom ? 'custom' : possible.type](this.args[this.params.length], possible, this.message, custom);
			return this.pushParam(res);
		} catch (err) {
			if (!this._required) {
				this.args.splice(this.params.length, 0, undefined);
				return this.pushParam(undefined);
			}

			const { response } = this._currentUsage;
			const error = typeof response === 'function' ? response(this.message, possible) : response;

			if (this._required === 1) return this.handleError(error || err);
			return this.handleError(error || (this.args[this.params.length] === undefined ?
				this.message.language.get('COMMANDMESSAGE_MISSING_REQUIRED', possible.name) :
				err)
			);
		}
	}

	/**
	 * Validates and resolves args into parameters, when multiple types of usage is defined
	 * @since 0.0.1
	 * @param {number} index The id of the possible usage currently being checked
	 * @returns {Promise<any[]>} The resolved parameters
	 * @private
	 */
	async multiPossibles(index) {
		const possible = this._currentUsage.possibles[index];
		const custom = this.usage.customResolvers[possible.type];

		if (possible.name in this.flags) this.args.splice(this.params.length, 0, this.flags[possible.name]);
		if (!this.client.argResolver[possible.type] && !custom) {
			this.client.emit('warn', `Unknown Argument Type encountered: ${possible.type}`);
			return this.multiPossibles(++index);
		}

		try {
			const res = await this.client.argResolver[custom ? 'custom' : possible.type](this.args[this.params.length], possible, this.message, custom);
			return this.pushParam(res);
		} catch (err) {
			if (index < this._currentUsage.possibles.length - 1) return this.multiPossibles(++index);
			if (!this._required) {
				this.args.splice(this.params.length, 0, undefined);
				return this.pushParam(undefined);
			}

			const { response } = this._currentUsage;
			const error = typeof response === 'function' ? response(this.message, possible) : response;

			if (this._required === 1) return this.handleError(error || err);
			return this.handleError(error || this.message.language.get('COMMANDMESSAGE_NOMATCH', this._currentUsage.possibles.map(poss => poss.name).join(', ')));
		}
	}

	/**
	 * Pushes a parameter into this.params, and resets the re-prompt count.
	 * @since 0.5.0
	 * @param {any} param The resolved parameter
	 * @returns {any[]}
	 * @private
	 */
	pushParam(param) {
		this._prompted = 0;
		this.params.push(param);
		return this.validateArgs();
	}

	/**
	 * Decides if the prompter should reprompt or throw the error found while validating.
	 * @since 0.5.0
	 * @param {string} err The error found while validating
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async handleError(err) {
		this.args.splice(this.params.length, 1, null);
		if (this.promptLimit && this._prompted < this.promptLimit) return this.reprompt(err);
		throw err;
	}

	/**
	 * Finalizes parameters and arguments for this prompt.
	 * @since 0.5.0
	 * @returns {any[]}
	 * @private
	 */
	finalize() {
		for (let i = this.params.length - 1; i >= 0 && this.params[i] === undefined; i--) this.params.pop();
		for (let i = this.args.length - 1; i >= 0 && this.args[i] === undefined; i--) this.args.pop();
		return this.params;
	}

	/**
	 * Splits the original message string into arguments.
	 * @since 0.5.0
	 * @param {string} original The original message string
	 * @returns {void}
	 * @private
	 */
	_setup(original) {
		const { content, flags } = this.constructor.getFlags(original, this.usage.usageDelim);
		this.flags = flags;
		this.args = this.quotedStringSupport ?
			this.constructor.getQuotedStringArgs(content, this.usage.usageDelim).map(arg => arg.trim()) :
			this.constructor.getArgs(content, this.usage.usageDelim).map(arg => arg.trim());
	}

	/**
	 * Parses a message into string args
	 * @since 0.5.0
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {Object}
	 * @private
	 */
	static getFlags(content, delim) {
		const flags = {};
		content = content.replace(TextPrompt.flagRegex, (match, fl, ...quote) => {
			flags[fl] = (quote.slice(0, -2).find(el => el) || fl).replace(/\\/g, '');
			return '';
		}).replace(new RegExp(`(?:(\\s)\\s+|(${delim})(?:${delim})+)`, 'g'), '$1').trim();
		return { content, flags };
	}

	/**
	 * Parses a message into string args
	 * @since 0.0.1
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {string[]}
	 * @private
	 */
	static getArgs(content, delim) {
		const args = content.replace(new RegExp(`(${delim})(?:${delim})+`, 'g'), '$1').split(delim !== '' ? delim : undefined);
		return args.length === 1 && args[0] === '' ? [] : args;
	}

	/**
	 * Parses a message into string args taking into account quoted strings
	 * @since 0.0.1
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {string[]}
	 * @private
	 */
	static getQuotedStringArgs(content, delim) {
		if (!delim || delim === '') return [content];

		const args = [];

		for (let i = 0; i < content.length; i++) {
			let current = '';
			if (content.slice(i, i + delim.length) === delim) {
				i += delim.length - 1;
				continue;
			}
			const quote = quotes.find(qt => qt.includes(content[i]));
			if (quote) {
				const qts = quote.split('');
				while (i + 1 < content.length && (content[i] === '\\' || !qts.includes(content[i + 1]))) current += content[++i] !== '\\' ? content[i] : '';
				i++;
				args.push(current);
			} else {
				current += content[i];
				while (i + 1 < content.length && content.slice(i + 1, i + delim.length + 1) !== delim) current += content[++i];
				args.push(current);
			}
		}

		return args.length === 1 && args[0] === '' ? [] : args;
	}

}

/**
 * Regular Expression to match flags with quoted string support.
 * @since 0.5.0
 * @type {RegExp}
 * @static
 */
TextPrompt.flagRegex = new RegExp(`--([\\w-]{2,})(?:=(?:${quotes.map(qu => `[${qu}]((?:[^${qu}\\\\]|\\\\.)*)[${qu}]`).join('|')}|([\\w-]+)))?`, 'g');

module.exports = TextPrompt;
<|endoftext|>


<commit_before>// This file is allowed to import `tweakable` objects from the other programs.
// Thanks to Rollup this does not blow up the bundle size.
/* eslint-disable import/no-restricted-paths */

// @flow strict-local

import * as React from "preact";

import {
  t as tBackground,
  tMeta as tMetaBackground,
} from "../background/Program";
import { t as tRenderer, tMeta as tMetaRenderer } from "../renderer/Program";
import {
  Resets,
  addListener,
  log,
  normalizeFiniteNumber,
} from "../shared/main";
import { type TweakableValue, stringArrayToSet } from "../shared/tweakable";
import {
  t as tElementManager,
  tMeta as tMetaElementManager,
} from "../worker/ElementManager";
import Field from "./Field";
import StringSetEditor, { equalStringSets } from "./StringSetEditor";
import TextInput from "./TextInput";

const ALL_TWEAKABLES = [
  [tBackground, tMetaBackground],
  [tRenderer, tMetaRenderer],
  [tElementManager, tMetaElementManager],
];

const ALL_KEYS: Set<string> = new Set(
  [].concat(
    ...ALL_TWEAKABLES.map(([, tMeta]) =>
      Object.keys(tMeta.defaults).map(key => `${tMeta.namespace}.${key}`)
    )
  )
);

type Props = {|
  before?: React.Node,
|};

type State = {||};

export default class Tweakable extends React.Component<Props, State> {
  resets: Resets = new Resets();

  componentDidMount() {
    this.resets.add(
      addListener(browser.storage.onChanged, (changes, areaName) => {
        if (areaName === "sync") {
          const didUpdate = Object.keys(changes).some(key => ALL_KEYS.has(key));
          if (didUpdate) {
            this.forceUpdate();
          }
        }
      })
    );
  }

  componentWillUnmount() {
    this.resets.reset();
  }

  render() {
    const { before } = this.props;

    return (

        {before}

        {ALL_TWEAKABLES.map(([t, tMeta]) =>
          Object.keys(tMeta.defaults)
            .sort()
            .map(key => {
              return (
                <TweakableField
                  key={`${tMeta.namespace}.${key}`}
                  namespace={tMeta.namespace}
                  name={key}
                  value={t[key]}
                  defaultValue={tMeta.defaults[key]}
                />
              );
            })
        )}

    );
  }
}

function TweakableField<T: TweakableValue>({
  namespace,
  name,
  value,
  defaultValue,
}: {|
  namespace: string,
  name: string,
  value: T,
  defaultValue: T,
|}) {
  const fullKey = `${namespace}.${name}`;
  const fieldProps = {
    id: fullKey,
    label: `${namespace}: ${name}`,
  };

  if (typeof value === "number" && typeof defaultValue === "number") {
    return (
      <Field
        {...fieldProps}
        changed={value !== defaultValue}
        render={({ id }) => (
          <TextInput
            id={id}
            style={{ width: "33%" }}
            savedValue={String(value)}
            normalize={newValue =>
              normalizeFiniteNumber(newValue, defaultValue)
            }
            save={newValue => {
              save(fullKey, Number(newValue));
            }}
          />
        )}
      />
    );
  }

  if (typeof value === "string" && typeof defaultValue === "string") {
    return (
      <Field
        {...fieldProps}
        changed={value !== defaultValue}
        render={({ id }) => (
          <TextInput
            id={id}
            style={{ width: "100%" }}
            savedValue={value}
            normalize={newValue => {
              const trimmed = newValue.trim();
              return trimmed === "" ? defaultValue : trimmed;
            }}
            save={newValue => {
              save(fullKey, newValue);
            }}
          />
        )}
      />
    );
  }

  if (value instanceof Set && defaultValue instanceof Set) {
    return (
      <Field
        {...fieldProps}
        changed={!equalStringSets(value, defaultValue)}
        render={({ id }) => (
          <StringSetEditor
            id={id}
            savedValue={value}
            save={newValue => {
              const newSet = stringArrayToSet(newValue);
              save(fullKey, newSet.size === 0 ? defaultValue : newValue);
            }}
          />
        )}
      />
    );
  }

  const types = new Set(
    [value, defaultValue].map(val => ({}.toString.call(val)))
  );
  return (
    <Field
      {...fieldProps}
      span
      changed={false}
      render={() => (

Unknown type {Array.from(types).join(" / ")}.

      )}
    />
  );
}

function save(key: string, value: mixed) {
  browser.storage.sync.set({ [key]: value }).catch(error => {
    log("error", "TweakableField", "Failed to save.", error);
  });
}
<commit_msg>POC tweakable validators<commit_after>// This file is allowed to import `tweakable` objects from the other programs.
// Thanks to Rollup this does not blow up the bundle size.
/* eslint-disable import/no-restricted-paths */

// @flow strict-local

import * as React from "preact";
import { map, string } from "tiny-decoders";

import {
  t as tBackground,
  tMeta as tMetaBackground,
} from "../background/Program";
import { t as tRenderer, tMeta as tMetaRenderer } from "../renderer/Program";
import {
  Resets,
  addListener,
  log,
  normalizeFiniteNumber,
} from "../shared/main";
import { type TweakableValue, stringArrayToSet } from "../shared/tweakable";
import {
  t as tElementManager,
  tMeta as tMetaElementManager,
} from "../worker/ElementManager";
import Field from "./Field";
import StringSetEditor, { equalStringSets } from "./StringSetEditor";
import TextInput from "./TextInput";

type Validator = ?(mixed) => void;

type Validators = { [string]: Validator };

const validatorsElementManager: {
  [key: $Keys<typeof tElementManager>]: Validator,
} = {
  SELECTOR_IMAGE: map(string, value => {
    document.querySelector(value);
  }),
};

const ALL_TWEAKABLES = [
  [tBackground, tMetaBackground, ({}: Validators)],
  [tRenderer, tMetaRenderer, ({}: Validators)],
  [
    tElementManager,
    tMetaElementManager,
    ({ ...validatorsElementManager }: Validators),
  ],
];

const ALL_KEYS: Set<string> = new Set(
  [].concat(
    ...ALL_TWEAKABLES.map(([, tMeta]) =>
      Object.keys(tMeta.defaults).map(key => `${tMeta.namespace}.${key}`)
    )
  )
);

console.log(Array.from(ALL_KEYS).join("\n"));

type Props = {|
  before?: React.Node,
|};

type State = {||};

export default class Tweakable extends React.Component<Props, State> {
  resets: Resets = new Resets();

  componentDidMount() {
    this.resets.add(
      addListener(browser.storage.onChanged, (changes, areaName) => {
        if (areaName === "sync") {
          const didUpdate = Object.keys(changes).some(key => ALL_KEYS.has(key));
          if (didUpdate) {
            this.forceUpdate();
          }
        }
      })
    );
  }

  componentWillUnmount() {
    this.resets.reset();
  }

  render() {
    const { before } = this.props;

    return (

        {before}

        {ALL_TWEAKABLES.map(([t, tMeta, validators]) =>
          Object.keys(tMeta.defaults)
            .sort()
            .map(key => {
              return (
                <TweakableField
                  key={`${tMeta.namespace}.${key}`}
                  namespace={tMeta.namespace}
                  name={key}
                  value={t[key]}
                  defaultValue={tMeta.defaults[key]}
                  validator={validators[key]}
                />
              );
            })
        )}

    );
  }
}

function TweakableField<T: TweakableValue>({
  namespace,
  name,
  value,
  defaultValue,
  validator,
}: {|
  namespace: string,
  name: string,
  value: T,
  defaultValue: T,
  validator: ?(mixed) => void,
|}) {
  const fullKey = `${namespace}.${name}`;
  const fieldProps = {
    id: fullKey,
    label: `${namespace}: ${name}`,
  };

  if (typeof value === "number" && typeof defaultValue === "number") {
    return (
      <Field
        {...fieldProps}
        changed={value !== defaultValue}
        render={({ id }) => (
          <TextInput
            id={id}
            style={{ width: "33%" }}
            savedValue={String(value)}
            normalize={newValue =>
              normalizeFiniteNumber(newValue, defaultValue)
            }
            save={newValue => {
              save(fullKey, Number(newValue), validator);
            }}
          />
        )}
      />
    );
  }

  if (typeof value === "string" && typeof defaultValue === "string") {
    return (
      <Field
        {...fieldProps}
        changed={value !== defaultValue}
        render={({ id }) => (
          <TextInput
            id={id}
            style={{ width: "100%" }}
            savedValue={value}
            normalize={newValue => {
              const trimmed = newValue.trim();
              return trimmed === "" ? defaultValue : trimmed;
            }}
            save={newValue => {
              save(fullKey, newValue, validator);
            }}
          />
        )}
      />
    );
  }

  if (value instanceof Set && defaultValue instanceof Set) {
    return (
      <Field
        {...fieldProps}
        changed={!equalStringSets(value, defaultValue)}
        render={({ id }) => (
          <StringSetEditor
            id={id}
            savedValue={value}
            save={newValue => {
              const newSet = stringArrayToSet(newValue);
              save(
                fullKey,
                newSet.size === 0 ? defaultValue : newValue,
                validator
              );
            }}
          />
        )}
      />
    );
  }

  const types = new Set(
    [value, defaultValue].map(val => ({}.toString.call(val)))
  );
  return (
    <Field
      {...fieldProps}
      span
      changed={false}
      render={() => (

Unknown type {Array.from(types).join(" / ")}.

      )}
    />
  );
}

async function save(key: string, value: mixed, validator: ?(mixed) => void) {
  try {
    if (validator != null) {
      validator(value);
    }
    await browser.storage.sync.set({ [key]: value });
  } catch (error) {
    log("error", "TweakableField", "Failed to save.", error);
  }
}
<|endoftext|>


<commit_before>var util = require('./util'),
	assert = util.assert
	q = util.q,
	debug = util.debug

var LPAREN = '(',
    RPAREN = ')',
    LBLOCK = '{',
    RBLOCK = '}'

var gToken, gIndex, gTokens, gState, gAST

exports.parse = function(tokens) {
	gTokens = tokens
	gIndex = -1
	gToken = null
	gAST = []

	while (true) {
		advance()
		if (gIndex == gTokens.length) { break }
		gAST.push(parseStatement())
	}

	return gAST
}

var parseStatement = function() {
	switch(gToken.type) {
		case 'string':
		case 'number':
			return getLiteralValue()
		case 'symbol':
			return parseXML() // only XML statements begin with a symbol (<)
		case 'name':
		    switch (gToken.value) {
        		case 'let': return parseDeclaration()
        		case 'for': return parseForLoop()
        		case 'if': return parseIfStatement()
        		default: return parseAlias()
        	}

		default:
			throw new Error('Unknown parse statement token: ' + JSON.stringify(gToken))
	}
}

/*******************
 * Utility methods *
 *******************/
var advance = function(type, value, expressionType) {
	gToken = gTokens[++gIndex]
	function check(v1, v2) {
		assert.equal(v1, v2,
			['Expected a', q(type),
				value ? 'of value ' + q(value) : '',
				expressionType ? 'for ' + expressionType : '',
				'on line:', gToken.line,
				'column:', gToken.column,
				'but found a', q(gToken.type),
				'of value', q(gToken.value)].join(' ')
	)}
	if (type) { check(type, gToken.type) }
	if (value) {
		if (value instanceof Array) { for (var i=0, val; val=value[i]; i++) {
			if (val != gToken.value) { continue }
			value = val // allow for the check to pass if one of the values passed in matches
			break
		}}
		check(value, gToken.value)
	}
}
var peek = function(amount) { return gTokens[gIndex + (amount || 1)] }
var isAhead = function(amount, type, value) {
	var token = gTokens[gIndex + amount]
	if (type && type != token.type) { return false }
	if (value && value != token.value) { return false }
	return true
}

/******************************
 * Aliases and literal values *
 ******************************/
function parseAlias() {
 // TODO Parse dot notation
 return { type:'ALIAS', name:gToken.value }
}

function getLiteralValue() {
	assert(gToken.type == 'string' || gToken.type == 'number')
	return { type:gToken.type.toUpperCase(), value:gToken.value }
}

/*******
 * XML *
 *******/
var parseXML = function() {
	debug('parseXML')
	advance('name', null, 'XML tag')
	var tagName = gToken.value,
		attributes = parseXMLAttributes()

	advance('symbol', ['>', '/'], 'end of XML tag')
	if (gToken.value == '/') {
		advance('symbol', '>', 'self-closing XML tag')
		return { type:'XML', tag:tagName, attributes:attributes, content:[] }
	} else {
		var statements = []
		while(true) {
			if (isAhead(1, 'symbol', '<') && isAhead(2, 'symbol', '/')) { break }
			advance()
			statements.push(parseStatement())
		}

		advance('symbol', '<')
		advance('symbol', '/')
		advance('name', tagName, 'matching XML tags')
		advance('symbol', '>')

		return { type:'XML', tag:tagName, attributes:attributes, block:statements }
	}
}

var parseXMLAttributes = function() {
	debug('parseXMLAttributes')
	if (!peek('name')) { return [] } // no attributes
}

/****************
 * Declarations *
 ****************/
function parseDeclaration() {
	debug('parseDeclaration')
	advance('name', null, 'declaration')
	var name = gToken.value
	advance('symbol', '=', 'declaration')
	value = parseDeclarable()
	return { type:'DECLARATION', name:name, value:value }
}
function parseDeclarable() {
	debug('parseDeclarable')
	advance()
	switch(gToken.type) {
		case 'name':
		    return parseAlias()
		case 'symbol':
			// TODO Parse JSON literal
			return parseXML()
		case 'string':
		case 'number':
			return getLiteralValue()
		default:
			throw new Error('Unkown declarable token: ' + JSON.stringify(gToken))
	}
}

/*************
 * For loops *
 *************/
function parseForLoop() {
	debug('parseForLoop')
	throw new Error("For loops are not yet implemented")
}

/****************
 * If statement *
 ****************/
function parseIfStatement() {
	debug('parseIfStatement')

    advance('symbol', LPAREN, 'beginning of the if statement\'s conditional')
    var condition = parseCondition()
    advance('symbol', RPAREN, 'end of the if statement\'s conditional')

    advance('symbol', LBLOCK, 'beginning of the if statement\'s block')
    var statements = []
    while(true) {
		if (isAhead(1, 'symbol', RBLOCK)) { break }
		advance()
		statements.push(parseStatement())
	}
    advance('symbol', RBLOCK, 'end of the if statement\'s block')

    return { type:'IF_STATEMENT', condition:condition, block:statements }
}

function parseCondition() {
    // TODO Implement condition parsing
    advance()
    return {}
}<commit_msg>Rename parseAlias to getAlias, since it doesn't advance any tokens - it just returns the value of the current token, assuming that it is an alias<commit_after>var util = require('./util'),
	assert = util.assert
	q = util.q,
	debug = util.debug

var LPAREN = '(',
    RPAREN = ')',
    LBLOCK = '{',
    RBLOCK = '}'

var gToken, gIndex, gTokens, gState, gAST

exports.parse = function(tokens) {
	gTokens = tokens
	gIndex = -1
	gToken = null
	gAST = []

	while (true) {
		advance()
		if (gIndex == gTokens.length) { break }
		gAST.push(parseStatement())
	}

	return gAST
}

var parseStatement = function() {
	switch(gToken.type) {
		case 'string':
		case 'number':
			return getLiteralValue()
		case 'symbol':
			return parseXML() // only XML statements begin with a symbol (<)
		case 'name':
		    switch (gToken.value) {
        		case 'let': return parseDeclaration()
        		case 'for': return parseForLoop()
        		case 'if': return parseIfStatement()
        		default: return getAlias()
        	}

		default:
			throw new Error('Unknown parse statement token: ' + JSON.stringify(gToken))
	}
}

/*******************
 * Utility methods *
 *******************/
var advance = function(type, value, expressionType) {
	gToken = gTokens[++gIndex]
	function check(v1, v2) {
		assert.equal(v1, v2,
			['Expected a', q(type),
				value ? 'of value ' + q(value) : '',
				expressionType ? 'for ' + expressionType : '',
				'on line:', gToken.line,
				'column:', gToken.column,
				'but found a', q(gToken.type),
				'of value', q(gToken.value)].join(' ')
	)}
	if (type) { check(type, gToken.type) }
	if (value) {
		if (value instanceof Array) { for (var i=0, val; val=value[i]; i++) {
			if (val != gToken.value) { continue }
			value = val // allow for the check to pass if one of the values passed in matches
			break
		}}
		check(value, gToken.value)
	}
}
var peek = function(amount) { return gTokens[gIndex + (amount || 1)] }
var isAhead = function(amount, type, value) {
	var token = gTokens[gIndex + amount]
	if (type && type != token.type) { return false }
	if (value && value != token.value) { return false }
	return true
}

/******************************
 * Aliases and literal values *
 ******************************/
function getAlias() {
 // TODO Parse dot notation
 return { type:'ALIAS', name:gToken.value }
}

function getLiteralValue() {
	assert(gToken.type == 'string' || gToken.type == 'number')
	return { type:gToken.type.toUpperCase(), value:gToken.value }
}

/*******
 * XML *
 *******/
var parseXML = function() {
	debug('parseXML')
	advance('name', null, 'XML tag')
	var tagName = gToken.value,
		attributes = parseXMLAttributes()

	advance('symbol', ['>', '/'], 'end of XML tag')
	if (gToken.value == '/') {
		advance('symbol', '>', 'self-closing XML tag')
		return { type:'XML', tag:tagName, attributes:attributes, content:[] }
	} else {
		var statements = []
		while(true) {
			if (isAhead(1, 'symbol', '<') && isAhead(2, 'symbol', '/')) { break }
			advance()
			statements.push(parseStatement())
		}

		advance('symbol', '<')
		advance('symbol', '/')
		advance('name', tagName, 'matching XML tags')
		advance('symbol', '>')

		return { type:'XML', tag:tagName, attributes:attributes, block:statements }
	}
}

var parseXMLAttributes = function() {
	debug('parseXMLAttributes')
	if (!peek('name')) { return [] } // no attributes
}

/****************
 * Declarations *
 ****************/
function parseDeclaration() {
	debug('parseDeclaration')
	advance('name', null, 'declaration')
	var name = gToken.value
	advance('symbol', '=', 'declaration')
	value = parseDeclarable()
	return { type:'DECLARATION', name:name, value:value }
}
function parseDeclarable() {
	debug('parseDeclarable')
	advance()
	switch(gToken.type) {
		case 'name':
		    return getAlias()
		case 'symbol':
			// TODO Parse JSON literal
			return parseXML()
		case 'string':
		case 'number':
			return getLiteralValue()
		default:
			throw new Error('Unkown declarable token: ' + JSON.stringify(gToken))
	}
}

/*************
 * For loops *
 *************/
function parseForLoop() {
	debug('parseForLoop')
	throw new Error("For loops are not yet implemented")
}

/****************
 * If statement *
 ****************/
function parseIfStatement() {
	debug('parseIfStatement')

    advance('symbol', LPAREN, 'beginning of the if statement\'s conditional')
    var condition = parseCondition()
    advance('symbol', RPAREN, 'end of the if statement\'s conditional')

    advance('symbol', LBLOCK, 'beginning of the if statement\'s block')
    var statements = []
    while(true) {
		if (isAhead(1, 'symbol', RBLOCK)) { break }
		advance()
		statements.push(parseStatement())
	}
    advance('symbol', RBLOCK, 'end of the if statement\'s block')

    return { type:'IF_STATEMENT', condition:condition, block:statements }
}

function parseCondition() {
    // TODO Implement condition parsing
    advance()
    return {}
}<|endoftext|>


<commit_before>/**
 * jquery.ztree_toc.js
 * data: 2014-01-25
 * author: alfred sang
 */

/**
 * 1.1.1 = 1*100*100 + 1*100 + 1
 * 1.2.2 = 1*100*100 + 2*100 + 3
 *
 * 1 = 0*100 +1
 */
function encode_id_with_array(opts,arr){
	console.log('---------------- get_id_with_str start---------------\n');

	var result = 0;
  	for(var z = 0; z < arr.length; z++ ){
		// str += opts.step+'*' +  opts.step + '*';

		result += factor(opts, arr.length - z ,arr[z]);
		console.log('z = ' + z + ',result=' ,result);
  	}

	console.log('result all = '+result);
	console.log('--------------end-----------------\n');

	return result;
}

/**
 * 1.1.1 = 1*100*100 + 1*100 + 1
 * 1.2.2 = 1*100*100 + 2*100 + 3
 *
 * 1 = 0*100 +1

	1,1 = 100

 */
function get_parent_id_with_array(opts,arr){
	console.log('---------------- get_id_with_str start---------------\n');
	var result_arr = [];

  	for(var z = 0; z < arr.length; z++ ){
		result_arr.push(arr[z]);
  	}

	result_arr.pop();

	var result = 0;
  	for(var z = 0; z < result_arr.length; z++ ){
		// str += opts.step+'*' +  opts.step + '*';

		result += factor(opts,result_arr.length - z,result_arr[z]);
		console.log('z = ' + z + ',result=',result);
  	}

	console.log('result all = '+result);
	console.log('--------------end-----------------\n');

	return result;
}

function factor(opts ,count,current){
	if(1 == count){
		return current;
	}

	var str = '';
	for(var i = count - 1;i > 0; i-- ){
		str += current * opts.step+'*';
	}

	console.log('str = '+str);

	return eval( str + '1' );
}

;(function($){
	/*
	 * 根据header创建目录内容
	 */
	function create_toc(opts){
		$(opts.documment_selector).find(':header').each(function() {
			var level = parseInt(this.nodeName.substring(1), 10);

			_rename_header_content(opts,this,level);

			_add_header_node(opts,$(this));
		});//end each
	}

	/*
	 * 渲染ztree
	 */
	function render_with_ztree(opts){
		var t = $(opts._zTree);
		t = $.fn.zTree.init(t,opts.ztreeSetting,opts._header_nodes).expandAll(opts.is_expand_all);
		// alert(opts._headers * 88);
		// $(opts._zTree).height(opts._headers  * 33 + 33);

		$(opts._zTree).css(opts.ztreeStyle);
	}

	/*
	 * 将已有header编号，并重命名
	 */
	function _rename_header_content(opts ,header_obj ,level){
		if(opts._headers.length == level) {
			opts._headers[level - 1]++;
		} else if(opts._headers.length > level) {
			opts._headers = opts._headers.slice(0, level);
			opts._headers[level - 1] ++;
		} else if(opts._headers.length < level) {
			for(var i = 0; i < (level - opts._headers.length); i++) {
			  // console.log('push 1');
			  opts._headers.push(1);
			}
		}

		if(opts.is_auto_number == true){
			$(header_obj).text(opts._headers.join('.') + '. ' + $(header_obj).text());
		}
	}

	/*
	 * 给ztree用的header_nodes增加数据
	 */
	function _add_header_node(opts ,header_obj){
		var id  = encode_id_with_array(opts,opts._headers);
		var pid = get_parent_id_with_array(opts,opts._headers);

      	// 设置锚点id
		$(header_obj).attr('id',id);

		log($(header_obj).text());

		opts._header_nodes.push({
			id:id,
			pId:pid ,
			name:$(header_obj).text()||'null',
			open:true,
			url:'#'+ id,
			target:'_self'
		});
	}

	function log(str){
		if($.fn.ztree_toc.defaults.debug == true){
			console.log(str);
		}
	}

	$.fn.ztree_toc = function(options){
		// 将defaults 和 options 参数合并到{}
		var opts = $.extend({},$.fn.ztree_toc.defaults,options);

		return this.each(function(){
			opts._zTree = $(this);

			create_toc(opts);

			render_with_ztree(opts);
		})
		// each end
	}

	//定义默认
	$.fn.ztree_toc.defaults = {
		_zTree: null,
		_headers: [],
		_header_nodes: [{ id:1, pId:0, name:"Table of Content",open:true}],
		debug: true,
		documment_selector: 'body',
		is_posion_top: false,
		/*
		 * 默认是否显示header编号
		 */
		is_auto_number: false,
		/*
		 * 默认是否展开全部
		 */
		is_expand_all: true,
		/*
		 * 是否对选中行，显示高亮效果
		 */
		is_highlight_selected_line: true,
		step: 100,
		ztreeStyle: {
			width:'260px',
			overflow: 'auto',
			position: 'fixed',
			'z-index': 2147483647,
			border: '0px none',
			left: '0px',
			bottom: '0px',
			// height:'100px'
		},
		ztreeSetting: {
			view: {
				dblClickExpand: false,
				showLine: true,
				showIcon: false,
				selectedMulti: false
			},
			data: {
				simpleData: {
					enable: true,
					idKey : "id",
					pIdKey: "pId",
					// rootPId: "0"
				}
			},
			callback: {
				beforeClick: function(treeId, treeNode) {
					if($.fn.ztree_toc.defaults.is_highlight_selected_line == true){
						$('#' + treeNode.id).css('color' ,'red').fadeOut("slow" ,function() {
						    // Animation complete.
							$(this).show().css('color','black');
						});
					}
				}
			}
		}
	};

})(jQuery);<commit_msg>add copyright<commit_after>/**
 * jquery.ztree_toc.js - A jQuery plugin that will automatically generate a table of contents.
 * v0.1.0
 * https://github.com/i5ting/jQuery.zTree_Toc.jss
 * created_at: 2014-01-25
 * author: alfred sang
 * MIT License
 */

/**
 * 1.1.1 = 1*100*100 + 1*100 + 1
 * 1.2.2 = 1*100*100 + 2*100 + 3
 *
 * 1 = 0*100 +1
 */
function encode_id_with_array(opts,arr){
	console.log('---------------- get_id_with_str start---------------\n');

	var result = 0;
  	for(var z = 0; z < arr.length; z++ ){
		// str += opts.step+'*' +  opts.step + '*';

		result += factor(opts, arr.length - z ,arr[z]);
		console.log('z = ' + z + ',result=' ,result);
  	}

	console.log('result all = '+result);
	console.log('--------------end-----------------\n');

	return result;
}

/**
 * 1.1.1 = 1*100*100 + 1*100 + 1
 * 1.2.2 = 1*100*100 + 2*100 + 3
 *
 * 1 = 0*100 +1

	1,1 = 100

 */
function get_parent_id_with_array(opts,arr){
	console.log('---------------- get_id_with_str start---------------\n');
	var result_arr = [];

  	for(var z = 0; z < arr.length; z++ ){
		result_arr.push(arr[z]);
  	}

	result_arr.pop();

	var result = 0;
  	for(var z = 0; z < result_arr.length; z++ ){
		// str += opts.step+'*' +  opts.step + '*';

		result += factor(opts,result_arr.length - z,result_arr[z]);
		console.log('z = ' + z + ',result=',result);
  	}

	console.log('result all = '+result);
	console.log('--------------end-----------------\n');

	return result;
}

function factor(opts ,count,current){
	if(1 == count){
		return current;
	}

	var str = '';
	for(var i = count - 1;i > 0; i-- ){
		str += current * opts.step+'*';
	}

	console.log('str = '+str);

	return eval( str + '1' );
}

;(function($){
	/*
	 * 根据header创建目录内容
	 */
	function create_toc(opts){
		$(opts.documment_selector).find(':header').each(function() {
			var level = parseInt(this.nodeName.substring(1), 10);

			_rename_header_content(opts,this,level);

			_add_header_node(opts,$(this));
		});//end each
	}

	/*
	 * 渲染ztree
	 */
	function render_with_ztree(opts){
		var t = $(opts._zTree);
		t = $.fn.zTree.init(t,opts.ztreeSetting,opts._header_nodes).expandAll(opts.is_expand_all);
		// alert(opts._headers * 88);
		// $(opts._zTree).height(opts._headers  * 33 + 33);

		$(opts._zTree).css(opts.ztreeStyle);
	}

	/*
	 * 将已有header编号，并重命名
	 */
	function _rename_header_content(opts ,header_obj ,level){
		if(opts._headers.length == level) {
			opts._headers[level - 1]++;
		} else if(opts._headers.length > level) {
			opts._headers = opts._headers.slice(0, level);
			opts._headers[level - 1] ++;
		} else if(opts._headers.length < level) {
			for(var i = 0; i < (level - opts._headers.length); i++) {
			  // console.log('push 1');
			  opts._headers.push(1);
			}
		}

		if(opts.is_auto_number == true){
			$(header_obj).text(opts._headers.join('.') + '. ' + $(header_obj).text());
		}
	}

	/*
	 * 给ztree用的header_nodes增加数据
	 */
	function _add_header_node(opts ,header_obj){
		var id  = encode_id_with_array(opts,opts._headers);
		var pid = get_parent_id_with_array(opts,opts._headers);

      	// 设置锚点id
		$(header_obj).attr('id',id);

		log($(header_obj).text());

		opts._header_nodes.push({
			id:id,
			pId:pid ,
			name:$(header_obj).text()||'null',
			open:true,
			url:'#'+ id,
			target:'_self'
		});
	}

	function log(str){
		if($.fn.ztree_toc.defaults.debug == true){
			console.log(str);
		}
	}

	$.fn.ztree_toc = function(options){
		// 将defaults 和 options 参数合并到{}
		var opts = $.extend({},$.fn.ztree_toc.defaults,options);

		return this.each(function(){
			opts._zTree = $(this);

			create_toc(opts);

			render_with_ztree(opts);
		})
		// each end
	}

	//定义默认
	$.fn.ztree_toc.defaults = {
		_zTree: null,
		_headers: [],
		_header_nodes: [{ id:1, pId:0, name:"Table of Content",open:true}],
		debug: true,
		documment_selector: 'body',
		is_posion_top: false,
		/*
		 * 默认是否显示header编号
		 */
		is_auto_number: false,
		/*
		 * 默认是否展开全部
		 */
		is_expand_all: true,
		/*
		 * 是否对选中行，显示高亮效果
		 */
		is_highlight_selected_line: true,
		step: 100,
		ztreeStyle: {
			width:'260px',
			overflow: 'auto',
			position: 'fixed',
			'z-index': 2147483647,
			border: '0px none',
			left: '0px',
			bottom: '0px',
			// height:'100px'
		},
		ztreeSetting: {
			view: {
				dblClickExpand: false,
				showLine: true,
				showIcon: false,
				selectedMulti: false
			},
			data: {
				simpleData: {
					enable: true,
					idKey : "id",
					pIdKey: "pId",
					// rootPId: "0"
				}
			},
			callback: {
				beforeClick: function(treeId, treeNode) {
					if($.fn.ztree_toc.defaults.is_highlight_selected_line == true){
						$('#' + treeNode.id).css('color' ,'red').fadeOut("slow" ,function() {
						    // Animation complete.
							$(this).show().css('color','black');
						});
					}
				}
			}
		}
	};

})(jQuery);<|endoftext|>


<commit_before>'use strict';
var React = require('react');

var paths = require('../paths');
var _ = require('lodash');
var config = require('config');

module.exports = {
  contextTypes: {
    router: React.PropTypes.func
  },

  getPathPrefix: function(pathName) {
    var prefix = '';

    // skip root paths
    if(pathName[1] !== '/') {
      prefix = pathName.split('/').map(function() {
        return '';
      }).join('../');
    }

    return prefix;
  },

  getAllItems: function () {
    return paths.allItems();
  },
  getSection: function () {
    var sectionName = this.getSectionName();
    var ret = config.paths[sectionName];

    if(!ret) {
      console.warn('Failed to find section ' +  sectionName + ' within ' +
        Object.keys(config.paths).join(', '));

      return {};
    }

    return ret;
  },
  getSectionTitle: function () {
    return this.getSection().title || '';
  },
  getSectionName: function () {
    let item = this.getItem();

    if(item.section) {
      return item.section;
    }

    // strip /
    return this.context.router.getCurrentPath().slice(1);
  },
  getSectionItems: function (sectionName) {
    sectionName = sectionName || this.getSectionName();

    return _.filter(this.getAllItems(), function (item) {
      return item.section == sectionName;
    });
  },
  getItem: function() {
    let router = this.context.router;
    let params = router.getCurrentParams();
    let item = params.item;
    let splat = params.splat;
    if(splat) {
      return this.getItemForPath(splat + '/' + item);
    }

    return this.getItemForPath(item ? item : '/index');
  },
  getItemForPath: function(path) {
    var ret = paths.itemForPath(path);

    if(!ret) {
      console.warn('No item was found for path ' + path + '!');
    }

    return ret;
  },
  // TODO: rename as getItemTitle?
  getPageTitle: function() {
    let item = this.getItem();

    if(item && item.title) {
      return item.title;
    }

    let router = this.context.router;
    let routes = router.getCurrentRoutes();

    let routeName = routes[routes.length - 1].name;

    if(routeName) {
      return routeName.replace('/', '');
    }

    return '';
  },
};
<commit_msg>Remove TODO<commit_after>'use strict';
var React = require('react');

var paths = require('../paths');
var _ = require('lodash');
var config = require('config');

module.exports = {
  contextTypes: {
    router: React.PropTypes.func
  },

  getPathPrefix: function(pathName) {
    var prefix = '';

    // skip root paths
    if(pathName[1] !== '/') {
      prefix = pathName.split('/').map(function() {
        return '';
      }).join('../');
    }

    return prefix;
  },

  getAllItems: function () {
    return paths.allItems();
  },
  getSection: function () {
    var sectionName = this.getSectionName();
    var ret = config.paths[sectionName];

    if(!ret) {
      console.warn('Failed to find section ' +  sectionName + ' within ' +
        Object.keys(config.paths).join(', '));

      return {};
    }

    return ret;
  },
  getSectionTitle: function () {
    return this.getSection().title || '';
  },
  getSectionName: function () {
    let item = this.getItem();

    if(item.section) {
      return item.section;
    }

    // strip /
    return this.context.router.getCurrentPath().slice(1);
  },
  getSectionItems: function (sectionName) {
    sectionName = sectionName || this.getSectionName();

    return _.filter(this.getAllItems(), function (item) {
      return item.section == sectionName;
    });
  },
  getItem: function() {
    let router = this.context.router;
    let params = router.getCurrentParams();
    let item = params.item;
    let splat = params.splat;
    if(splat) {
      return this.getItemForPath(splat + '/' + item);
    }

    return this.getItemForPath(item ? item : '/index');
  },
  getItemForPath: function(path) {
    var ret = paths.itemForPath(path);

    if(!ret) {
      console.warn('No item was found for path ' + path + '!');
    }

    return ret;
  },
  getPageTitle: function() {
    let item = this.getItem();

    if(item && item.title) {
      return item.title;
    }

    let router = this.context.router;
    let routes = router.getCurrentRoutes();

    let routeName = routes[routes.length - 1].name;

    if(routeName) {
      return routeName.replace('/', '');
    }

    return '';
  },
};
<|endoftext|>


<commit_before>const layoutTemplate = `

`;

const Articles = Backbone.Collection.extend({
  url: 'http://localhost:1337/articles'
});

const LayoutView = Marionette.LayoutView.extend({
  el: 'main',
  template: _.template(layoutTemplate),
  regions: {
    header: '#header',
    drawer: '#drawer',
    content: '#content',
    toast: '#toast'
  },
  onRender() {
    this.getRegion('content').show(new Marionette.Form({
      text: 'Eres mayor de edad?',
      buttons: [
        {
          label: 'SI',
          onClick: function() {
            $('.mdl-layout__drawer-button').css({ display: 'initial' });
            Backbone.history.navigate('/order', true);
          }
        },
        {
          label: 'NO',
          onClick: function() {
            document.location.href = 'http://www.google.com';
          }
        }
      ]
    }));
  }
});

const layout = new LayoutView();

layout.render();

const User = Backbone.Model.extend({
  urlRoot: 'http://localhost:1337/users',
  idAttribute: '_id',
});

const user = new User({ _id: '56d28dabb23bf0423c8e12a9' });
user.fetch().then(() => {
  layout.getRegion('drawer').show(new Marionette.Modelo.DrawerView({
    menu: [{
      label: '  Pedir ahora',
      href: '/order'
    }, {
      label: '  Historial de pedidos',
      href: '/orders'
    }, {
      label: '  Mi Perfil',
      href: '/profile'
    }, {
      label: '  Eventos',
      href: '/events'
    }],
    user: user
  }));
});
//layout.getRegion('header').show(new Marionette.form());

const articles = new Articles();
const Router = Marionette.AppRouter.extend({
  appRoutes: {
    'order': 'order',
    'orders': 'orders',
    'events': 'events',
    'profile': 'profile'
  },
  controller: {
    order() {
      var self = this;
      this._showTotal = _.debounce(this._showTotal.bind(this), 500);
      this._closeTotal = _.debounce(this._closeTotal, 1600);
      articles.fetch().done(function(){
        articles.listenTo(articles, 'change', self._showTotal);
      });
      layout.getRegion('content').show(new Marionette.ArticleList({ collection: articles }))
    },
    orders() {

    },
    events() {

    },
    profile() {

    },
    _showTotal: function() {
      var total = 0;
      articles.each(function(article){
        total+= parseFloat(article.get('currentPrice'));
      });
      total = total.toFixed(2);
      var showTotal = new Marionette.ShowTotal({
        total: total
      });
      layout.getRegion('toast').show(showTotal);
      $('#toast').addClass('active');
      this._closeTotal();
    },
    _closeTotal: function() {
      console.log('close total')
      $('#toast').removeClass('active');
    }
  },
  onRoute: function () {
    if (document.body.querySelector('.mdl-layout__obfuscator.is-visible')) {
      document.body.querySelector('.mdl-layout__obfuscator.is-visible').click();
    }
  }
});

if (document.location.pathname != '/') {
  document.location.href = '/';
}

new Router();

Backbone.Intercept.start();
Backbone.history.start({
  pushState: true
});

const userChannel = Backbone.Radio.channel('user');

const saveCurrentPosition = () => {
  navigator.geolocation.getCurrentPosition(function(position) {
      var latitude = position.coords.latitude.toFixed(3);
      var longitude = position.coords.longitude.toFixed(3);
      user.set('location', [longitude, latitude]);
      user.save();
  });
};

setTimeout(saveCurrentPosition, 1000*60*30);
saveCurrentPosition();

<commit_msg>INT: fix show total events<commit_after>const layoutTemplate = `

`;

const Articles = Backbone.Collection.extend({
  url: 'http://localhost:1337/articles'
});

const LayoutView = Marionette.LayoutView.extend({
  el: 'main',
  template: _.template(layoutTemplate),
  regions: {
    header: '#header',
    drawer: '#drawer',
    content: '#content',
    toast: '#toast'
  },
  onRender() {
    this.getRegion('content').show(new Marionette.Form({
      text: 'Eres mayor de edad?',
      buttons: [
        {
          label: 'SI',
          onClick: function() {
            $('.mdl-layout__drawer-button').css({ display: 'initial' });
            Backbone.history.navigate('/order', true);
          }
        },
        {
          label: 'NO',
          onClick: function() {
            document.location.href = 'http://www.google.com';
          }
        }
      ]
    }));
  }
});

const layout = new LayoutView();

layout.render();

const User = Backbone.Model.extend({
  urlRoot: 'http://localhost:1337/users',
  idAttribute: '_id',
});

const user = new User({ _id: '56d28dabb23bf0423c8e12a9' });
user.fetch().then(() => {
  layout.getRegion('drawer').show(new Marionette.Modelo.DrawerView({
    menu: [{
      label: '  Pedir ahora',
      href: '/order'
    }, {
      label: '  Historial de pedidos',
      href: '/orders'
    }, {
      label: '  Mi Perfil',
      href: '/profile'
    }, {
      label: '  Eventos',
      href: '/events'
    }],
    user: user
  }));
});
//layout.getRegion('header').show(new Marionette.form());

const Router = Marionette.AppRouter.extend({
  appRoutes: {
    'order': 'order',
    'orders': 'orders',
    'events': 'events',
    'profile': 'profile'
  },
  controller: {
    order() {
      var self = this;
      var articles = new Articles();
      this.articles = articles
      this._showTotalDebounce = _.debounce(this._showTotal.bind(this), 500);
      this._closeTotalDebounce = _.debounce(this._closeTotal, 1600);
      articles.fetch().done(function(){
        articles.listenTo(articles, 'change', self._showTotalDebounce);
      });
      layout.getRegion('content').show(new Marionette.ArticleList({
        collection: articles
      }))
    },
    orders() {

    },
    events() {

    },
    profile() {

    },
    _showTotal: function() {
      console.log('show total');
      var total = 0;
      this.articles.each(function(article){
        total+= parseFloat(article.get('currentPrice'));
      });
      total = total.toFixed(2);
      var showTotal = new Marionette.ShowTotal({
        total: total
      });
      layout.getRegion('toast').show(showTotal);
      $('#toast').addClass('active');
      this._closeTotalDebounce();
    },
    _closeTotal: function() {
      console.log('close total')
      $('#toast').removeClass('active');
    }
  },
  onRoute: function () {
    if (document.body.querySelector('.mdl-layout__obfuscator.is-visible')) {
      document.body.querySelector('.mdl-layout__obfuscator.is-visible').click();
    }
  }
});

if (document.location.pathname != '/') {
  document.location.href = '/';
}

new Router();

Backbone.Intercept.start();
Backbone.history.start({
  pushState: true
});

const userChannel = Backbone.Radio.channel('user');

const saveCurrentPosition = () => {
  navigator.geolocation.getCurrentPosition(function(position) {
      var latitude = position.coords.latitude.toFixed(3);
      var longitude = position.coords.longitude.toFixed(3);
      user.set('location', [longitude, latitude]);
      user.save();
  });
};

setTimeout(saveCurrentPosition, 1000*60*30);
saveCurrentPosition();

<|endoftext|>


<commit_before>//-----------------------------------
//
// window.DOJO.Write: write data to HTML
// -- Made by window.DOJO.Setup
//-----------------------------------

window.DOJO.Write = function(setup){
  this.share = setup.share;
};
window.DOJO.Write.prototype = {
  totalIds: 0,
  copy: function(id,i){
    return document.getElementById(id).children[i].cloneNode(1);
  },
  grandparent: function(el){
    return el.parentElement.parentElement;
  },
  grandkid: function(el,A){
    return A.reduce(function(el,i){
      return el.children[i];
    },el);
  },
  head: function(source,parent,cousin){
    var self = this.copy("proto",0);
    var offspring = this.grandkid(self,[1,1]);
    var id = this.totalIds++;
    var factsheet = [
      ["id", "in"+id],
      ["for", "in"+id],
      ["id", source.self]
    ];
    factsheet.forEach(function(tag,tagi){
      var temp = self.children[tagi];
      temp.setAttribute.apply(temp,tag);
    });
    self.children[0].checked = true;
    parent.appendChild(self);
    offspring.children[0].innerHTML = source.name;
    cousin.children[1].innerHTML = source.length;
  },
  body: function(source,parent,cousin){
    var grandparent = this.grandparent(cousin);
    var ancestor = this.grandparent(grandparent);
    var uncle = this.grandkid(ancestor,[1,1]);
    var size = source.dimensions;
    var dtype = source["data-type"];
    var channel = Number(!dtype.match(/float|uint8/)) + source.channel;
    var old = source.old.replace(/&channel=([^&]+)/,"&channel="+channel);
    var [w,h,d] = [Math.max(size.x,512),Math.max(size.y,512),size.z];
    var path = "viz.html?depth="+d+"&width="+w+"&height="+h;
    // Get the path for this channel
    this_path = path + "&" + old;
    // Set the current group href
    current = cousin.children[0];
    current.setAttribute("href", this_path);
    // Set the containing group href
    group = uncle.children[0]
    if (group.href) {
      // Add the current channel to the containing group
      group.setAttribute("href", group.href + "," + channel);
    }
    else {
      // Add the full path to the containing group
      group.setAttribute("href", this_path);
    }
    cousin.children[1].innerHTML = dtype;
    uncle.children[1].innerHTML = [w,h,d].join(", ");
    grandparent.children[0].checked = false;
    ancestor.children[0].checked = false;
    var factsheet = [
      ["description", source["short-description"] || source["name"]],
      ["datasource", source.datasource || "unknown"],
      ["path", source.path || "unknown"]
    ];
    factsheet.forEach(function(items){
      var temp = this.copy("proto",2);
      var info = this.grandkid(temp,[0,0]);
      info.children[0].innerHTML = items[0];
      info.children[1].innerHTML = items[1];
      parent.appendChild(temp);
    },this);
  },
  main: function(terms){
    var source = this.share(terms,{self:terms.self.join(",")});
    var parent = document.getElementById(terms.parent.join(",") || "0");
    var cousin = this.grandkid(parent.parentElement,[1,1]);
    this[source.target](source,parent,cousin);
    return terms;
  }
}
<commit_msg>viewer: add missing var statements<commit_after>//-----------------------------------
//
// window.DOJO.Write: write data to HTML
// -- Made by window.DOJO.Setup
//-----------------------------------

window.DOJO.Write = function(setup){
  this.share = setup.share;
};
window.DOJO.Write.prototype = {
  totalIds: 0,
  copy: function(id,i){
    return document.getElementById(id).children[i].cloneNode(1);
  },
  grandparent: function(el){
    return el.parentElement.parentElement;
  },
  grandkid: function(el,A){
    return A.reduce(function(el,i){
      return el.children[i];
    },el);
  },
  head: function(source,parent,cousin){
    var self = this.copy("proto",0);
    var offspring = this.grandkid(self,[1,1]);
    var id = this.totalIds++;
    var factsheet = [
      ["id", "in"+id],
      ["for", "in"+id],
      ["id", source.self]
    ];
    factsheet.forEach(function(tag,tagi){
      var temp = self.children[tagi];
      temp.setAttribute.apply(temp,tag);
    });
    self.children[0].checked = true;
    parent.appendChild(self);
    offspring.children[0].innerHTML = source.name;
    cousin.children[1].innerHTML = source.length;
  },
  body: function(source,parent,cousin){
    var grandparent = this.grandparent(cousin);
    var ancestor = this.grandparent(grandparent);
    var uncle = this.grandkid(ancestor,[1,1]);
    var size = source.dimensions;
    var dtype = source["data-type"];
    var channel = Number(!dtype.match(/float|uint8/)) + source.channel;
    var old = source.old.replace(/&channel=([^&]+)/,"&channel="+channel);
    var [w,h,d] = [Math.max(size.x,512),Math.max(size.y,512),size.z];
    var path = "viz.html?depth="+d+"&width="+w+"&height="+h;
    // Get the path for this channel
    var this_path = path + "&" + old;
    // Set the current group href
    var current = cousin.children[0];
    current.setAttribute("href", this_path);
    // Set the containing group href
    var group = uncle.children[0];
    if (group.href) {
      // Add the current channel to the containing group
      group.setAttribute("href", group.href + "," + channel);
    }
    else {
      // Add the full path to the containing group
      group.setAttribute("href", this_path);
    }
    cousin.children[1].innerHTML = dtype;
    uncle.children[1].innerHTML = [w,h,d].join(", ");
    grandparent.children[0].checked = false;
    ancestor.children[0].checked = false;
    var factsheet = [
      ["description", source["short-description"] || source["name"]],
      ["datasource", source.datasource || "unknown"],
      ["path", source.path || "unknown"]
    ];
    factsheet.forEach(function(items){
      var temp = this.copy("proto",2);
      var info = this.grandkid(temp,[0,0]);
      info.children[0].innerHTML = items[0];
      info.children[1].innerHTML = items[1];
      parent.appendChild(temp);
    },this);
  },
  main: function(terms){
    var source = this.share(terms,{self:terms.self.join(",")});
    var parent = document.getElementById(terms.parent.join(",") || "0");
    var cousin = this.grandkid(parent.parentElement,[1,1]);
    this[source.target](source,parent,cousin);
    return terms;
  }
}
<|endoftext|>


<commit_before>if( typeof(BiwaScheme)!='object' ) BiwaScheme={}; with(BiwaScheme) {

  //
  // interface to javascript
  //
  define_libfunc("js-eval", 1, 1, function(ar){
    return eval(ar[0]);
  });
  define_libfunc("js-ref", 2, 2, function(ar){
    if(_.isString(ar[1])){
      return ar[0][ar[1]];
    }
    else{
      assert_symbol(ar[1]);
      return ar[0][ar[1].name];
    }
  });
  define_libfunc("js-set!", 3, 3, function(ar){
    assert_string(ar[1]);
    ar[0][ar[1]] = ar[2];
    return BiwaScheme.undef;
  });

  // (js-call (js-eval "Math.pow") 2 4)
  define_libfunc("js-call", 1, null, function(ar){
    var js_func = ar.shift();
    assert_function(js_func);

    var receiver = null;
    return js_func.apply(receiver, ar);
  });
  // (js-invoke (js-new "Date") "getTime")
  define_libfunc("js-invoke", 2, null, function(ar){
    var js_obj = ar.shift();
    var func_name = ar.shift();
    if(!_.isString(func_name)){
      assert_symbol(func_name);
      func_name = func_name.name;
    }
    if(js_obj[func_name])
      return js_obj[func_name].apply(js_obj, ar);
    else
      throw new Error("js-invoke: function "+func_name+" is not defined");
  });

  // Short hand for JavaScript method call.
  //
  // (js-invocation obj '(foo 1 2 3))  ;=> obj.foo(1,2,3)
  // (js-invocation obj '(foo 1 2 3)   ;=> obj.foo(1,2,3)
  //                    'bar           ;      .bar
  //                    '(baz 4 5))    ;      .baz(4,5)
  // (js-invocation 'Math '(pow 2 3))  ;=> Math.pow(2,3)
  //
  // It also converts
  //   (lambda (e) ...) to
  //   (js-closure (lambda (e) ...))
  //   and
  //   '((a . b) (c . 4)) to
  //   {a: "b", c: 4}
  //
  define_libfunc("js-invocation", 2, null, function(ar, intp){
    var receiver = ar.shift();
    // TODO: convert lambdas by js-closure
    if(BiwaScheme.isSymbol(receiver)){
      receiver = eval(receiver.name); //XXX: is this ok?
    }

    var v = receiver;

    // Process each method call
    _.each(ar, function(callspec){
        if(BiwaScheme.isSymbol(callspec)){
          // Property access
          v = v[callspec.name];
        }
        else if(BiwaScheme.isList(callspec)){
          // Method call
          var args = callspec.to_array();

          assert_symbol(args[0]);
          var method = args.shift().name;

          // Convert arguments
          args = _.map(args, function(arg){
              if(BiwaScheme.isClosure(arg)){
                // closure -> JavaScript funciton
                return BiwaScheme.js_closure(arg, intp);
              }
              else if(BiwaScheme.isList(arg)){
                // alist -> JavaScript Object
                var o = {};
                arg.foreach(function(pair){
                    assert_symbol(pair.car);
                    o[pair.car.name] = pair.cdr;
                  });
                return o;
              }
              else
                return arg;
            });

          // Call the method
          if(!_.isFunction(v[method])){
            throw new BiwaScheme.Error("js-invocation: the method `"+method+"' not found");
          }
          v = v[method].apply(v, args);
        }
        else{
          // (wrong argument)
          throw new BiwaScheme.Error("js-invocation: expected list or symbol for callspec but got " + BiwaScheme.inspect(callspec));
        }
      });

    return v;
  });

  // TODO: provide corresponding macro ".."
  define_syntax("..", function(x){
    if (x.cdr == nil) {
      throw new Error("malformed ..");
    }
    return new Pair(Sym("js-invocation"), x.cdr);
  });

  // (js-new (js-eval "Date") 2005 1 1)
  // (js-new (js-eval "Draggable") elem 'onEnd (lambda (drg) ...))
  //   If symbol is given, following arguments are converted to
  //   an js object. If any of them is a scheme closure,
  //   it is converted to js function which invokes that closure.
  //
  // (js-new "Date" 2005 1 1)
  //   You can pass javascript program string for constructor.
  define_libfunc("js-new", 1, null, function(ar, intp){
    // make js object from key-value pair
    var array_to_obj = function(ary){
      if((ary.length % 2) != 0)
        throw new Error("js-new: odd number of key-value pair");

      var obj = {};
      for(var i=0; i<ary.length; i+=2){
        var key = ary[i], value = ary[i+1];
        assert_symbol(key);
        if(value.closure_p === true)
          value = BiwaScheme.js_closure(value, intp);

        obj[key.name] = value;
      }
      return obj;
    };

    var ctor = ar.shift();
    if (_.isString(ctor)) ctor = eval(ctor);
    if(ar.length == 0){
      return new ctor();
    }
    else{
      // pack args to js object, if symbol appears
      var args = [];
      for(var i=0; i<ar.length; i++){
        if(ar[i] instanceof Symbol){
          args.push(array_to_obj(ar.slice(i)));
          break;
        }
        else{
          args.push(ar[i]);
        }
      }
      // construct js code to create new object
      var args_str = _.map(ar, function(value, i){
        return "args['" + i + "']";
      }).join(",");
      return eval("new ctor(" + args_str + ")");
    }
  });

  // (js-obj "foo" 1 "bar" 2)
  // -> {"foo": 1, "bar": 2}
  define_libfunc("js-obj", 0, null, function(ar){
    if(ar.length % 2 != 0){
      throw new Error("js-obj: number of arguments must be even");
    }

    var obj = {};
    for(i=0; i<ar.length/2; i++){
      assert_string(ar[i*2]);
      obj[ar[i*2]] = ar[i*2+1];
    }
    return obj;
  });

  BiwaScheme.js_closure = function(proc, intp){
    var intp2 = new Interpreter(intp);
    return function(/*args*/){
      return intp2.invoke_closure(proc, _.toArray(arguments));
    };
  };
  // (js-closure (lambda (event) ..))
  // Returns a js function which executes the given procedure.
  //
  // Example
  //   (add-handler! ($ "#btn") "click" (js-closure on-click))
  define_libfunc("js-closure", 1, 1, function(ar, intp){
    assert_closure(ar[0]);
    return BiwaScheme.js_closure(ar[0], intp);
  });

  define_libfunc("js-null?", 1, 1, function(ar){
    return ar[0] === null;
  });

  define_libfunc("js-undefined?", 1, 1, function(ar){
    return ar[0] === undefined;
  });

  define_libfunc("js-function?", 1, 1, function(ar){
    return _.isFunction(ar[0]);
  });

  define_libfunc("js-array-to-list", 1, 1, function(ar){
    BiwaScheme.deprecate("js-array-to-list", "1.0", "js-array->list");
    return BiwaScheme.array_to_list(ar[0]);
  });

  define_libfunc("js-array->list", 1, 1, function(ar){
    return BiwaScheme.array_to_list(ar[0]);
  });

  define_libfunc("list-to-js-array", 1, 1, function(ar){
    BiwaScheme.deprecate("list-to-js-array", "1.0", "list->js-array");
    return ar[0].to_array();
  });

  define_libfunc("list->js-array", 1, 1, function(ar){
    return ar[0].to_array();
  });

  BiwaScheme.alist_to_js_obj = function(alist) {
    if (alist === nil) {
      return {} ;
    }
    assert_list(alist);
    var obj = {};
    alist.foreach(function(item){
      assert_string(item.car);
      obj[item.car] = item.cdr;
    });
    return obj;
  };
  define_libfunc("alist-to-js-obj", 1, 1, function(ar) {
    BiwaScheme.deprecate("alist-to-js-obj", "1.0", "alist->js-obj");
    return BiwaScheme.alist_to_js_obj(ar[0]);
  });

  define_libfunc("alist->js-obj", 1, 1, function(ar) {
    return BiwaScheme.alist_to_js_obj(ar[0]);
  });

  BiwaScheme.js_obj_to_alist = function(obj) {
    if (obj === undefined) {
      return BiwaScheme.nil;
    }
    var arr = [];
    _.each(obj, function(val, key) {
      arr.push(new Pair(key, val));
    });
    var alist = BiwaScheme.array_to_list(arr);
    return alist;
  };
  define_libfunc("js-obj-to-alist", 1, 1, function(ar) {
    BiwaScheme.deprecate("js-obj-to-alist", "1.0", "js-obj->alist");
    return BiwaScheme.js_obj_to_alist(ar[0]);
  });
  define_libfunc("js-obj->alist", 1, 1, function(ar) {
    return BiwaScheme.js_obj_to_alist(ar[0]);
  });

  //
  // timer, sleep
  //
  define_libfunc("timer", 2, 2, function(ar, intp){
    var proc = ar[0], sec = ar[1];
    assert_closure(proc);
    assert_real(sec);
    var intp2 = new Interpreter(intp);
    setTimeout(function(){ intp2.invoke_closure(proc); }, sec * 1000);
    return BiwaScheme.undef;
  });
  define_libfunc("set-timer!", 2, 2, function(ar, intp){
    var proc = ar[0], sec = ar[1];
    assert_closure(proc);
    assert_real(sec);
    var intp2 = new Interpreter(intp);
    return setInterval(function(){ intp2.invoke_closure(proc); }, sec * 1000);
  });
  define_libfunc("clear-timer!", 1, 1, function(ar){
    var timer_id = ar[0];
    clearInterval(timer_id);
    return BiwaScheme.undef;
  });
  define_libfunc("sleep", 1, 1, function(ar){
    var sec = ar[0];
    assert_real(sec);
    return new BiwaScheme.Pause(function(pause){
      setTimeout(function(){ pause.resume(nil); }, sec * 1000);
    });
  });

  //
  // console
  //
  // (console-debug obj1 ...)
  // (console-log obj1 ...)
  // (console-info obj1 ...)
  // (console-warn obj1 ...)
  // (console-error obj1 ...)
  //   Put objects to console, if window.console is defined.
  //   Returns obj1.
  //
  // Example:
  //     (some-func arg1 (console-debug arg2) arg3)
  var define_console_func = function(name){
    define_libfunc("console-"+name, 1, null, function(ar){
      var con = window.console;
      if(con){
        var vals = _.map(ar, function(item){
          return BiwaScheme.inspect(item, {fallback: item});
        });

        con[name].apply(con, vals);
      }
      return ar[0];
    });
  };
  define_console_func("debug");
  define_console_func("log");
  define_console_func("info");
  define_console_func("warn");
  define_console_func("error");

}
<commit_msg>Simplify js-new with Function.prototype.bind<commit_after>if( typeof(BiwaScheme)!='object' ) BiwaScheme={}; with(BiwaScheme) {

  //
  // interface to javascript
  //
  define_libfunc("js-eval", 1, 1, function(ar){
    return eval(ar[0]);
  });
  define_libfunc("js-ref", 2, 2, function(ar){
    if(_.isString(ar[1])){
      return ar[0][ar[1]];
    }
    else{
      assert_symbol(ar[1]);
      return ar[0][ar[1].name];
    }
  });
  define_libfunc("js-set!", 3, 3, function(ar){
    assert_string(ar[1]);
    ar[0][ar[1]] = ar[2];
    return BiwaScheme.undef;
  });

  // (js-call (js-eval "Math.pow") 2 4)
  define_libfunc("js-call", 1, null, function(ar){
    var js_func = ar.shift();
    assert_function(js_func);

    var receiver = null;
    return js_func.apply(receiver, ar);
  });
  // (js-invoke (js-new "Date") "getTime")
  define_libfunc("js-invoke", 2, null, function(ar){
    var js_obj = ar.shift();
    var func_name = ar.shift();
    if(!_.isString(func_name)){
      assert_symbol(func_name);
      func_name = func_name.name;
    }
    if(js_obj[func_name])
      return js_obj[func_name].apply(js_obj, ar);
    else
      throw new Error("js-invoke: function "+func_name+" is not defined");
  });

  // Short hand for JavaScript method call.
  //
  // (js-invocation obj '(foo 1 2 3))  ;=> obj.foo(1,2,3)
  // (js-invocation obj '(foo 1 2 3)   ;=> obj.foo(1,2,3)
  //                    'bar           ;      .bar
  //                    '(baz 4 5))    ;      .baz(4,5)
  // (js-invocation 'Math '(pow 2 3))  ;=> Math.pow(2,3)
  //
  // It also converts
  //   (lambda (e) ...) to
  //   (js-closure (lambda (e) ...))
  //   and
  //   '((a . b) (c . 4)) to
  //   {a: "b", c: 4}
  //
  define_libfunc("js-invocation", 2, null, function(ar, intp){
    var receiver = ar.shift();
    // TODO: convert lambdas by js-closure
    if(BiwaScheme.isSymbol(receiver)){
      receiver = eval(receiver.name); //XXX: is this ok?
    }

    var v = receiver;

    // Process each method call
    _.each(ar, function(callspec){
        if(BiwaScheme.isSymbol(callspec)){
          // Property access
          v = v[callspec.name];
        }
        else if(BiwaScheme.isList(callspec)){
          // Method call
          var args = callspec.to_array();

          assert_symbol(args[0]);
          var method = args.shift().name;

          // Convert arguments
          args = _.map(args, function(arg){
              if(BiwaScheme.isClosure(arg)){
                // closure -> JavaScript funciton
                return BiwaScheme.js_closure(arg, intp);
              }
              else if(BiwaScheme.isList(arg)){
                // alist -> JavaScript Object
                var o = {};
                arg.foreach(function(pair){
                    assert_symbol(pair.car);
                    o[pair.car.name] = pair.cdr;
                  });
                return o;
              }
              else
                return arg;
            });

          // Call the method
          if(!_.isFunction(v[method])){
            throw new BiwaScheme.Error("js-invocation: the method `"+method+"' not found");
          }
          v = v[method].apply(v, args);
        }
        else{
          // (wrong argument)
          throw new BiwaScheme.Error("js-invocation: expected list or symbol for callspec but got " + BiwaScheme.inspect(callspec));
        }
      });

    return v;
  });

  // TODO: provide corresponding macro ".."
  define_syntax("..", function(x){
    if (x.cdr == nil) {
      throw new Error("malformed ..");
    }
    return new Pair(Sym("js-invocation"), x.cdr);
  });

  // (js-new (js-eval "Date") 2005 1 1)
  // (js-new (js-eval "Draggable") elem 'onEnd (lambda (drg) ...))
  //   If symbol is given, following arguments are converted to
  //   an js object. If any of them is a scheme closure,
  //   it is converted to js function which invokes that closure.
  //
  // (js-new "Date" 2005 1 1)
  //   You can pass javascript program string for constructor.
  define_libfunc("js-new", 1, null, function(ar, intp){
    // make js object from key-value pair
    var array_to_obj = function(ary){
      if((ary.length % 2) != 0)
        throw new Error("js-new: odd number of key-value pair");

      var obj = {};
      for(var i=0; i<ary.length; i+=2){
        var key = ary[i], value = ary[i+1];
        assert_symbol(key);
        if(value.closure_p === true)
          value = BiwaScheme.js_closure(value, intp);

        obj[key.name] = value;
      }
      return obj;
    };

    var ctor = ar.shift();
    if (_.isString(ctor)) ctor = eval(ctor);

    if(ar.length == 0){
      return new ctor();
    }
    else{
      // pack args to js object, if symbol appears
      var args = [];
      for(var i=0; i<ar.length; i++){
        if(ar[i] instanceof Symbol){
          args.push(array_to_obj(ar.slice(i)));
          break;
        }
        else{
          args.push(ar[i]);
        }
      }
      // Run `new ctor(...args)`;
      return new (Function.prototype.bind.apply(ctor, [null].concat(args)))();
    }
  });

  // (js-obj "foo" 1 "bar" 2)
  // -> {"foo": 1, "bar": 2}
  define_libfunc("js-obj", 0, null, function(ar){
    if(ar.length % 2 != 0){
      throw new Error("js-obj: number of arguments must be even");
    }

    var obj = {};
    for(i=0; i<ar.length/2; i++){
      assert_string(ar[i*2]);
      obj[ar[i*2]] = ar[i*2+1];
    }
    return obj;
  });

  BiwaScheme.js_closure = function(proc, intp){
    var intp2 = new Interpreter(intp);
    return function(/*args*/){
      return intp2.invoke_closure(proc, _.toArray(arguments));
    };
  };
  // (js-closure (lambda (event) ..))
  // Returns a js function which executes the given procedure.
  //
  // Example
  //   (add-handler! ($ "#btn") "click" (js-closure on-click))
  define_libfunc("js-closure", 1, 1, function(ar, intp){
    assert_closure(ar[0]);
    return BiwaScheme.js_closure(ar[0], intp);
  });

  define_libfunc("js-null?", 1, 1, function(ar){
    return ar[0] === null;
  });

  define_libfunc("js-undefined?", 1, 1, function(ar){
    return ar[0] === undefined;
  });

  define_libfunc("js-function?", 1, 1, function(ar){
    return _.isFunction(ar[0]);
  });

  define_libfunc("js-array-to-list", 1, 1, function(ar){
    BiwaScheme.deprecate("js-array-to-list", "1.0", "js-array->list");
    return BiwaScheme.array_to_list(ar[0]);
  });

  define_libfunc("js-array->list", 1, 1, function(ar){
    return BiwaScheme.array_to_list(ar[0]);
  });

  define_libfunc("list-to-js-array", 1, 1, function(ar){
    BiwaScheme.deprecate("list-to-js-array", "1.0", "list->js-array");
    return ar[0].to_array();
  });

  define_libfunc("list->js-array", 1, 1, function(ar){
    return ar[0].to_array();
  });

  BiwaScheme.alist_to_js_obj = function(alist) {
    if (alist === nil) {
      return {} ;
    }
    assert_list(alist);
    var obj = {};
    alist.foreach(function(item){
      assert_string(item.car);
      obj[item.car] = item.cdr;
    });
    return obj;
  };
  define_libfunc("alist-to-js-obj", 1, 1, function(ar) {
    BiwaScheme.deprecate("alist-to-js-obj", "1.0", "alist->js-obj");
    return BiwaScheme.alist_to_js_obj(ar[0]);
  });

  define_libfunc("alist->js-obj", 1, 1, function(ar) {
    return BiwaScheme.alist_to_js_obj(ar[0]);
  });

  BiwaScheme.js_obj_to_alist = function(obj) {
    if (obj === undefined) {
      return BiwaScheme.nil;
    }
    var arr = [];
    _.each(obj, function(val, key) {
      arr.push(new Pair(key, val));
    });
    var alist = BiwaScheme.array_to_list(arr);
    return alist;
  };
  define_libfunc("js-obj-to-alist", 1, 1, function(ar) {
    BiwaScheme.deprecate("js-obj-to-alist", "1.0", "js-obj->alist");
    return BiwaScheme.js_obj_to_alist(ar[0]);
  });
  define_libfunc("js-obj->alist", 1, 1, function(ar) {
    return BiwaScheme.js_obj_to_alist(ar[0]);
  });

  //
  // timer, sleep
  //
  define_libfunc("timer", 2, 2, function(ar, intp){
    var proc = ar[0], sec = ar[1];
    assert_closure(proc);
    assert_real(sec);
    var intp2 = new Interpreter(intp);
    setTimeout(function(){ intp2.invoke_closure(proc); }, sec * 1000);
    return BiwaScheme.undef;
  });
  define_libfunc("set-timer!", 2, 2, function(ar, intp){
    var proc = ar[0], sec = ar[1];
    assert_closure(proc);
    assert_real(sec);
    var intp2 = new Interpreter(intp);
    return setInterval(function(){ intp2.invoke_closure(proc); }, sec * 1000);
  });
  define_libfunc("clear-timer!", 1, 1, function(ar){
    var timer_id = ar[0];
    clearInterval(timer_id);
    return BiwaScheme.undef;
  });
  define_libfunc("sleep", 1, 1, function(ar){
    var sec = ar[0];
    assert_real(sec);
    return new BiwaScheme.Pause(function(pause){
      setTimeout(function(){ pause.resume(nil); }, sec * 1000);
    });
  });

  //
  // console
  //
  // (console-debug obj1 ...)
  // (console-log obj1 ...)
  // (console-info obj1 ...)
  // (console-warn obj1 ...)
  // (console-error obj1 ...)
  //   Put objects to console, if window.console is defined.
  //   Returns obj1.
  //
  // Example:
  //     (some-func arg1 (console-debug arg2) arg3)
  var define_console_func = function(name){
    define_libfunc("console-"+name, 1, null, function(ar){
      var con = window.console;
      if(con){
        var vals = _.map(ar, function(item){
          return BiwaScheme.inspect(item, {fallback: item});
        });

        con[name].apply(con, vals);
      }
      return ar[0];
    });
  };
  define_console_func("debug");
  define_console_func("log");
  define_console_func("info");
  define_console_func("warn");
  define_console_func("error");

}
<|endoftext|>


<commit_before>'use strict';

describe('Controller: MainCtrl', function () {

  // load the controller's module
  beforeEach(module('mytodoApp'));

  var MainCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    MainCtrl = $controller('MainCtrl', {
      $scope: scope
    });
  }));

  /*Useful Properties in the Object Prototype*/
  it('you can use valueOf to retrieve the primitive type associated with an object', function () {
    var fiveNumber = 5;
    var fiveString = "5";

    expect(fiveNumber.valueOf()).toBe(5);
    expect(fiveString.valueOf()).toBe("5");
    // '==' operator performs Type Coercion which turns a number contained in a string into an actual number
    expect(fiveNumber.valueOf() == fiveString.valueOf()).toBe(true);
    // '===' operator does not ignore the type of the value
    expect(fiveNumber.valueOf() === fiveString.valueOf()).toBe(false);

  });

  it('you can use valueOf to retrieve the type of a Prototype', function () {
    function Person (firstName ){
      this.firstName = firstName;
    }
    Person.prototype = {
      address: "House 123"
    };
    var person = new Person("Martin");
    expect(person.valueOf()).toEqual(Object({ firstName: 'Martin' }));
  });

  it('you can override properties of prototypes, even if these are inherited', function () {
    function Person (firstName ){
      this.firstName = firstName;
    }
    Person.prototype.valueOf = function() {
      return this.firstName;
    };
    var person = new Person("Martin");
    expect(person.valueOf()).toBe("Martin");
    person.firstName = "Corrie";
    //the value of ValueOf is retrieved every time, even if the object changes
    expect(person.valueOf()).toBe("Corrie");

  });

  it('you can use toString() method to convert variables into a string', function () {
    var fiveNumber = 5;
    var fiveString = "5";

    expect(fiveNumber.toString()).toBe("5");
    //there is no effect for strings
    expect(fiveString.toString()).toBe("5");

    var fiveDotTwo = 5.2; //toString() also works with numbers and decimals
    expect(fiveDotTwo.toString()).toBe("5.2");

    var array = [1,2,3]; //toString() converts Arrays into a comma-separated string
    expect(array.toString()).toBe("1,2,3");

  });

  it('you can use constructor keyword to find the nearest prototype ancestor ', function () {

    function Person (firstName ){
      this.firstName = firstName;
    }
    Person.prototype.lastName = function() {
      return "Chavez";
    };
    var person = new Person("Martin");

    expect(person.constructor.name).toEqual("Person");
    //the prototype of a Class is an object
    expect(typeof(person.constructor.prototype)).toEqual('object');
    //the prototype method 'lastName' of Person is a function which returns a string
    expect(typeof(person.constructor.prototype.lastName())).toEqual('string');
    expect(person.constructor.prototype.lastName()).toEqual('Chavez');//every instance of the class Person share the prototype property
    expect(person.__proto__.lastName()).toEqual('Chavez');//another way of finding a Class Prototype is with the keyword '__proto__'
  });

  it('you can use hasOwnProperty to identify the property location', function () {

    function Person (firstName ){
      this.firstName = firstName;
    }
    Person.prototype.lastName = function() {
      return "Chavez";
    };
    var person = new Person("Martin");

    expect(person.hasOwnProperty('lastName')).toEqual(false);
    expect(person.constructor.hasOwnProperty('lastName')).toEqual(false);
    expect(person.__proto__.hasOwnProperty('lastName')).toEqual(true);
  });

});
<commit_msg>Objects<commit_after>'use strict';

describe('Controller: MainCtrl', function () {

  // load the controller's module
  beforeEach(module('mytodoApp'));

  var MainCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    MainCtrl = $controller('MainCtrl', {
      $scope: scope
    });
  }));

  /*Useful Properties in the Object Prototype*/
  it('you can use valueOf to retrieve the primitive type associated with an object', function () {
    var fiveNumber = 5;
    var fiveString = "5";

    expect(fiveNumber.valueOf()).toBe(5);
    expect(fiveString.valueOf()).toBe("5");
    // '==' operator performs Type Coercion which turns a number contained in a string into an actual number
    expect(fiveNumber.valueOf() == fiveString.valueOf()).toBe(true);
    // '===' operator does not ignore the type of the value
    expect(fiveNumber.valueOf() === fiveString.valueOf()).toBe(false);

  });

  it('you can use valueOf to retrieve the type of a Prototype', function () {
    function Person (firstName ){
      this.firstName = firstName;
    }
    Person.prototype = {
      address: "House 123"
    };
    var person = new Person("Martin");
    expect(person.valueOf()).toEqual(Object({ firstName: 'Martin' }));
  });

  it('you can override properties of prototypes, even if these are inherited', function () {
    function Person (firstName ){
      this.firstName = firstName;
    }
    Person.prototype.valueOf = function() {
      return this.firstName;
    };
    var person = new Person("Martin");
    expect(person.valueOf()).toBe("Martin");
    person.firstName = "Corrie";
    //the value of ValueOf is retrieved every time, even if the object changes
    expect(person.valueOf()).toBe("Corrie");

  });

  it('you can use toString() method to convert variables into a string', function () {
    var fiveNumber = 5;
    var fiveString = "5";

    expect(fiveNumber.toString()).toBe("5");
    //there is no effect in using toString() for strings
    expect(fiveString.toString()).toBe("5");

    var fiveDotTwo = 5.2; //toString() also works with numbers and decimals
    expect(fiveDotTwo.toString()).toBe("5.2");

    var array = [1,2,3]; //toString() converts Arrays into a comma-separated string
    expect(array.toString()).toBe("1,2,3");

  });

  it('you can use constructor keyword to find the nearest prototype ancestor ', function () {

    function Person (firstName ){
      this.firstName = firstName;
    }
    Person.prototype.lastName = function() {
      return "Chavez";
    };
    var person = new Person("Martin");

    expect(person.constructor.name).toEqual("Person");
    //the prototype of a Class is an object
    expect(typeof(person.constructor.prototype)).toEqual('object');
    //the prototype method 'lastName' of Person is a function which returns a string
    expect(typeof(person.constructor.prototype.lastName())).toEqual('string');
    expect(person.constructor.prototype.lastName()).toEqual('Chavez');//every instance of the class Person share the prototype property
    expect(person.__proto__.lastName()).toEqual('Chavez');//another way of finding a Class Prototype is with the keyword '__proto__'
  });

  it('you can use hasOwnProperty to identify the property location', function () {

    function Person (firstName ){
      this.firstName = firstName;
    }
    Person.prototype.lastName = function() {
      return "Chavez";
    };
    var person = new Person("Martin");

    expect(person.hasOwnProperty('lastName')).toEqual(false);
    expect(person.constructor.hasOwnProperty('lastName')).toEqual(false);
    expect(person.__proto__.hasOwnProperty('lastName')).toEqual(true);
  });

});
<|endoftext|>