function(input) {
			var output = [];
			var chr1, chr2, chr3, enc1, enc2, enc3, enc4, cdp1, cdp2, cdp3;
			var i = 0, il = input.length, b64 = chars;

			while (i < il) {

				cdp1 = input.charCodeAt(i++);
				cdp2 = input.charCodeAt(i++);
				cdp3 = input.charCodeAt(i++);

				chr1 = cdp1 & 0xff;
				chr2 = cdp2 & 0xff;
				chr3 = cdp3 & 0xff;

				enc1 = chr1 >> 2;
				enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
				enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
				enc4 = chr3 & 63;

				if (isNaN(cdp2)) {
					enc3 = enc4 = 64;
				} else if (isNaN(cdp3)) {
					enc4 = 64;
				}

				output.push(b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4));
			}

			return output.join('');
		}

function ( input ) { var output = [ ] ; var chr1 , chr2 , chr3 , enc1 , enc2 , enc3 , enc4 , cdp1 , cdp2 , cdp3 ; var i = 0 , il = input . length , b64 = chars ; while ( i < il ) { cdp1 = input . charCodeAt ( i ++ ) ; cdp2 = input . charCodeAt ( i ++ ) ; cdp3 = input . charCodeAt ( i ++ ) ; chr1 = cdp1 & 0xff ; chr2 = cdp2 & 0xff ; chr3 = cdp3 & 0xff ; enc1 = chr1 >> 2 ; enc2 = ( ( chr1 & 3 ) << 4 ) | ( chr2 >> 4 ) ; enc3 = ( ( chr2 & 15 ) << 2 ) | ( chr3 >> 6 ) ; enc4 = chr3 & 63 ; if ( isNaN ( cdp2 ) ) { enc3 = enc4 = 64 ; } else if ( isNaN ( cdp3 ) ) { enc4 = 64 ; } output . push ( b64 . charAt ( enc1 ) + b64 . charAt ( enc2 ) + b64 . charAt ( enc3 ) + b64 . charAt ( enc4 ) ) ; } return output . join ( '' ) ; }

Encodes data using base64 algorithm
@author Tyler Akins (http://rumkin.com)
@param {String} input
@returns {String}
@memberOf emmet.base64


function(data) {
			var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmpArr = [];
			var b64 = chars, il = data.length;

			if (!data) {
				return data;
			}

			data += '';

			do { // unpack four hexets into three octets using index points in b64
				h1 = b64.indexOf(data.charAt(i++));
				h2 = b64.indexOf(data.charAt(i++));
				h3 = b64.indexOf(data.charAt(i++));
				h4 = b64.indexOf(data.charAt(i++));

				bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;

				o1 = bits >> 16 & 0xff;
				o2 = bits >> 8 & 0xff;
				o3 = bits & 0xff;

				if (h3 == 64) {
					tmpArr[ac++] = String.fromCharCode(o1);
				} else if (h4 == 64) {
					tmpArr[ac++] = String.fromCharCode(o1, o2);
				} else {
					tmpArr[ac++] = String.fromCharCode(o1, o2, o3);
				}
			} while (i < il);

			return tmpArr.join('');
		}

Decodes string using MIME base64 algorithm

@author Tyler Akins (http://rumkin.com)
@param {String} data
@return {String}


function createMatcher(text) {
		var memo = {}, m;
		return {
			/**
			 * Test if given position matches opening tag
			 * @param {Number} i
			 * @returns {Object} Matched tag object
			 */
			open: function(i) {
				var m = this.matches(i);
				return m && m.type == 'open' ? m : null;
			},

			/**
			 * Test if given position matches closing tag
			 * @param {Number} i
			 * @returns {Object} Matched tag object
			 */
			close: function(i) {
				var m = this.matches(i);
				return m && m.type == 'close' ? m : null;
			},

			/**
			 * Matches either opening or closing tag for given position
			 * @param i
			 * @returns
			 */
			matches: function(i) {
				var key = 'p' + i;

				if (!(key in memo)) {
					if (text.charAt(i) == '<') {
						var substr = text.slice(i);
						if (m = substr.match(reOpenTag)) {
							memo[key] = openTag(i, m);
						} else if (m = substr.match(reCloseTag)) {
							memo[key] = closeTag(i, m);
						} else {
							// remember that given position contains no valid tag
							memo[key] = false;
						}
					}
				}

				return memo[key];
			},

			/**
			 * Returns original text
			 * @returns {String}
			 */
			text: function() {
				return text;
			}
		};
	}

Creates new tag matcher session
@param {String} text


function(node, offset) {
			var maxNum = 0;
			var options = {
				tabstop: function(data) {
					var group = parseInt(data.group);
					if (group > maxNum) maxNum = group;

					if (data.placeholder)
						return '${' + (group + offset) + ':' + data.placeholder + '}';
					else
						return '${' + (group + offset) + '}';
				}
			};

			_.each(['start', 'end', 'content'], function(p) {
				node[p] = this.processText(node[p], options);
			}, this);

			return maxNum;
		}

function ( node , offset ) { var maxNum = 0 ; var options = { tabstop : function ( data ) { var group = parseInt ( data . group ) ; if ( group > maxNum ) maxNum = group ; if ( data . placeholder ) return '${' + ( group + offset ) + ':' + data . placeholder + '}' ; else return '${' + ( group + offset ) + '}' ; } } ; _ . each ( [ 'start' , 'end' , 'content' ] , function ( p ) { node [ p ] = this . processText ( node [ p ] , options ) ; } , this ) ; return maxNum ; }

Upgrades tabstops in output node in order to prevent naming conflicts
@param {AbbreviationNode} node
@param {Number} offset Tab index offset
@returns {Number} Maximum tabstop index in element


function(name, value, description) {
			var prefs = name;
			if (_.isString(name)) {
				prefs = {};
				prefs[name] = {
					value: value,
					description: description
				};
			}

			_.each(prefs, function(v, k) {
				defaults[k] = isValueObj(v) ? v : {value: v};
			});
		}

function ( name , value , description ) { var prefs = name ; if ( _ . isString ( name ) ) { prefs = { } ; prefs [ name ] = { value : value , description : description } ; } _ . each ( prefs , function ( v , k ) { defaults [ k ] = isValueObj ( v ) ? v : { value : v } ; } ) ; }

Creates new preference item with default value
@param {String} name Preference name. You can also pass object
with many options
@param {Object} value Preference default value
@param {String} description Item textual description
@memberOf preferences


function(name) {
			if (name in preferences)
				return preferences[name];

			if (name in defaults)
				return defaults[name].value;

			return void 0;
		}

function ( name ) { if ( name in preferences ) return preferences [ name ] ; if ( name in defaults ) return defaults [ name ] . value ; return void 0 ; }

Returns preference value
@param {String} name
@returns {String} Returns undefined if preference is
not defined


function(name) {
			var val = this.get(name);
			if (_.isUndefined(val) || val === null || val === '')  {
				return null;
			}

			val = _.map(val.split(','), require('utils').trim);
			if (!val.length) {
				return null;
			}

			return val;
		}

function ( name ) { var val = this . get ( name ) ; if ( _ . isUndefined ( val ) || val === null || val === '' ) { return null ; } val = _ . map ( val . split ( ',' ) , require ( 'utils' ) . trim ) ; if ( ! val . length ) { return null ; } return val ; }

Returns comma-separated preference value as array of values
@param {String} name
@returns {Array} Returns undefined if preference is
not defined, null if string cannot be converted to array

Returns comma - separated preference value as array of values


function(name) {
			var result = {};
			_.each(this.getArray(name), function(val) {
				var parts = val.split(':');
				result[parts[0]] = parts[1];
			});

			return result;
		}

function ( name ) { var result = { } ; _ . each ( this . getArray ( name ) , function ( val ) { var parts = val . split ( ':' ) ; result [ parts [ 0 ] ] = parts [ 1 ] ; } ) ; return result ; }

Returns comma and colon-separated preference value as dictionary
@param {String} name
@returns {Object}

Returns comma and colon - separated preference value as dictionary


function(tree, filters, profile) {
			var utils = require('utils');
			profile = require('profile').get(profile);

			_.each(list(filters), function(filter) {
				var name = utils.trim(filter.toLowerCase());
				if (name && name in registeredFilters) {
					tree = registeredFilters[name](tree, profile);
				}
			});

			return tree;
		}

function ( tree , filters , profile ) { var utils = require ( 'utils' ) ; profile = require ( 'profile' ) . get ( profile ) ; _ . each ( list ( filters ) , function ( filter ) { var name = utils . trim ( filter . toLowerCase ( ) ) ; if ( name && name in registeredFilters ) { tree = registeredFilters [ name ] ( tree , profile ) ; } } ) ; return tree ; }

Apply filters for final output tree
@param {AbbreviationNode} tree Output tree
@param {Array} filters List of filters to apply. Might be a
String
@param {Object} profile Output profile, defined in profile
module. Filters defined it profile are not used, profile
is passed to filter function
@memberOf emmet.filters
@returns {AbbreviationNode}


function(syntax, profile, additionalFilters) {
			profile = require('profile').get(profile);
			var filters = list(profile.filters || require('resources').findItem(syntax, 'filters') || basicFilters);

			if (profile.extraFilters) {
				filters = filters.concat(list(profile.extraFilters));
			}

			if (additionalFilters) {
				filters = filters.concat(list(additionalFilters));
			}

			if (!filters || !filters.length) {
				// looks like unknown syntax, apply basic filters
				filters = list(basicFilters);
			}

			return filters;
		}

Composes list of filters that should be applied to a tree, based on
passed data
@param {String} syntax Syntax name ('html', 'css', etc.)
@param {Object} profile Output profile
@param {String} additionalFilters List or pipe-separated
string of additional filters to apply
@returns {Array}

Composes list of filters that should be applied to a tree based on passed data


function(value, start, end) {
			// create modification range
			var r = range(start, _.isUndefined(end) ? 0 : end - start);
			var delta = value.length - r.length();

			var update = function(obj) {
				_.each(obj, function(v, k) {
					if (v >= r.end)
						obj[k] += delta;
				});
			};

			// update affected positions of current container
			update(this._positions);

			// update affected positions of children
			_.each(this.list(), function(item) {
				update(item._positions);
			});

			this.source = require('utils').replaceSubstring(this.source, value, r);
		}

Replace substring of tag's source
@param {String} value
@param {Number} start
@param {Number} end
@private


function(name) {
			if (_.isNumber(name))
				return this.list()[name];

			if (_.isString(name))
				return _.find(this.list(), function(prop) {
					return prop.name() === name;
				});

			return name;
		}

function ( name ) { if ( _ . isNumber ( name ) ) return this . list ( ) [ name ] ; if ( _ . isString ( name ) ) return _ . find ( this . list ( ) , function ( prop ) { return prop . name ( ) === name ; } ) ; return name ; }

Returns attribute object
@param {String} name Attribute name or its index
@returns {EditElement}


function(name) {
			if (!_.isArray(name))
				name = [name];

			// split names and indexes
			var names = [], indexes = [];
			_.each(name, function(item) {
				if (_.isString(item))
					names.push(item);
				else if (_.isNumber(item))
					indexes.push(item);
			});

			return _.filter(this.list(), function(attribute, i) {
				return _.include(indexes, i) || _.include(names, attribute.name());
			});
		}

function
(
name
)
{
if
(
!
_
.
isArray
(
name
)
)
name
=
[
name
]
;
// split names and indexes
var
names
=
[
]
,
indexes
=
[
]
;
_
.
each
(
name
,
function
(
item
)
{
if
(
_
.
isString
(
item
)
)
names
.
push
(
item
)
;
else
if
(
_
.
isNumber
(
item
)
)
indexes
.
push
(
item
)
;
}
)
;
return
_
.
filter
(
this
.
list
(
)
,
function
(
attribute
,
i
)
{
return
_
.
include
(
indexes
,
i
)
||
_
.
include
(
names
,
attribute
.
name
(
)
)
;
}
)
;
}

Returns all children by name or indexes
@param {Object} name Element name(s) or indexes (String,
Array, Number)
@returns {Array}


function(name, value, pos) {
			var element = this.get(name);
			if (element)
				return element.value(value);

			if (!_.isUndefined(value)) {
				// no such element — create it
				return this.add(name, value, pos);
			}
		}

function
(
name
,
value
,
pos
)
{
var
element
=
this
.
get
(
name
)
;
if
(
element
)
return
element
.
value
(
value
)
;
if
(
!
_
.
isUndefined
(
value
)
)
{
// no such element — create it
return
this
.
add
(
name
,
value
,
pos
)
;
}
}

Returns or updates element value. If such element doesn't exists,
it will be created automatically and added at the end of child list.
@param {String} name Element name or its index
@param {String} value New element value
@returns {String}

Returns or updates element value . If such element doesn t exists it will be created automatically and added at the end of child list .


function(name) {
			var element = this.get(name);
			if (element) {
				this._updateSource('', element.fullRange());
				this._children = _.without(this._children, element);
			}
		}

function ( name ) { var element = this . get ( name ) ; if ( element ) { this . _updateSource ( '' , element . fullRange ( ) ) ; this . _children = _ . without ( this . _children , element ) ; } }

Remove child element
@param {String} name Property name or its index


function(val) {
			if (!_.isUndefined(val) && this._name !== (val = String(val))) {
				this._updateSource(val, this._positions.name, this._positions.name + this._name.length);
				this._name = val;
			}

			return this._name;
		}

function ( val ) { if ( ! _ . isUndefined ( val ) && this . _name !== ( val = String ( val ) ) ) { this . _updateSource ( val , this . _positions . name , this . _positions . name + this . _name . length ) ; this . _name = val ; } return this . _name ; }

Sets or gets container name
@param {String} val New name. If not passed, current
name is returned
@return {String}


function(pos, isAbsolute) {
			return _.find(this.list(), function(elem) {
				return elem.range(isAbsolute).inside(pos);
			});
		}

function ( pos , isAbsolute ) { return _ . find ( this . list ( ) , function ( elem ) { return elem . range ( isAbsolute ) . inside ( pos ) ; } ) ; }

Returns element that belongs to specified position
@param {Number} pos
@param {Boolean} isAbsolute
@returns {EditElement}


function(val) {
			if (!_.isUndefined(val) && this._value !== (val = String(val))) {
				this.parent._updateSource(val, this.valueRange());
				this._value = val;
			}

			return this._value;
		}

function ( val ) { if ( ! _ . isUndefined ( val ) && this . _value !== ( val = String ( val ) ) ) { this . parent . _updateSource ( val , this . valueRange ( ) ) ; this . _value = val ; } return this . _value ; }

Sets of gets element value
@param {String} val New element value. If not passed, current
value is returned
@returns {String}


function(val) {
			if (!_.isUndefined(val) && this._name !== (val = String(val))) {
				this.parent._updateSource(val, this.nameRange());
				this._name = val;
			}

			return this._name;
		}

function ( val ) { if ( ! _ . isUndefined ( val ) && this . _name !== ( val = String ( val ) ) ) { this . parent . _updateSource ( val , this . nameRange ( ) ) ; this . _name = val ; } return this . _name ; }

Sets of gets element name
@param {String} val New element name. If not passed, current
name is returned
@returns {String}


function trimWhitespaceTokens(tokens, mask) {
		mask = mask || (WHITESPACE_REMOVE_FROM_START | WHITESPACE_REMOVE_FROM_END);
		var whitespace = ['white', 'line'];

		if ((mask & WHITESPACE_REMOVE_FROM_END) == WHITESPACE_REMOVE_FROM_END)
			while (tokens.length && _.include(whitespace, _.last(tokens).type)) {
				tokens.pop();
	 		}

		if ((mask & WHITESPACE_REMOVE_FROM_START) == WHITESPACE_REMOVE_FROM_START)
			while (tokens.length && _.include(whitespace, tokens[0].type)) {
				tokens.shift();
			}

		return tokens;
	}

function trimWhitespaceTokens ( tokens , mask ) { mask = mask || ( WHITESPACE_REMOVE_FROM_START | WHITESPACE_REMOVE_FROM_END ) ; var whitespace = [ 'white' , 'line' ] ; if ( ( mask & WHITESPACE_REMOVE_FROM_END ) == WHITESPACE_REMOVE_FROM_END ) while ( tokens . length && _ . include ( whitespace , _ . last ( tokens ) . type ) ) { tokens . pop ( ) ; } if ( ( mask & WHITESPACE_REMOVE_FROM_START ) == WHITESPACE_REMOVE_FROM_START ) while ( tokens . length && _ . include ( whitespace , tokens [ 0 ] . type ) ) { tokens . shift ( ) ; } return tokens ; }

Removes whitespace tokens from the array ends
@param {Array} tokens
@param {Number} mask Mask indicating from which end whitespace should be
removed
@returns {Array}


function findValueRange(it) {
		// find value start position
		var skipTokens = ['white', 'line', ':'];
		var tokens = [], token, start, end;

		it.nextUntil(function(tok) {
			return !_.include(skipTokens, this.itemNext().type);
		});

		start = it.current().end;
		// consume value
		while (token = it.next()) {
			if (token.type == '}' || token.type == ';') {
				// found value end
				trimWhitespaceTokens(tokens, WHITESPACE_REMOVE_FROM_START
						| (token.type == '}' ? WHITESPACE_REMOVE_FROM_END : 0));

				if (tokens.length) {
					start = tokens[0].start;
					end = _.last(tokens).end;
				} else {
					end = start;
				}

				return range(start, end - start);
			}

			tokens.push(token);
		}

		// reached the end of tokens list
		if (tokens.length) {
			return range(tokens[0].start, _.last(tokens).end - tokens[0].start);
		}
	}

function
findValueRange
(
it
)
{
// find value start position
var
skipTokens
=
[
'white'
,
'line'
,
':'
]
;
var
tokens
=
[
]
,
token
,
start
,
end
;
it
.
nextUntil
(
function
(
tok
)
{
return
!
_
.
include
(
skipTokens
,
this
.
itemNext
(
)
.
type
)
;
}
)
;
start
=
it
.
current
(
)
.
end
;
// consume value
while
(
token
=
it
.
next
(
)
)
{
if
(
token
.
type
==
'}'
||
token
.
type
==
';'
)
{
// found value end
trimWhitespaceTokens
(
tokens
,
WHITESPACE_REMOVE_FROM_START
|
(
token
.
type
==
'}'
WHITESPACE_REMOVE_FROM_END
:
0
)
)
;
if
(
tokens
.
length
)
{
start
=
tokens
[
0
]
.
start
;
end
=
_
.
last
(
tokens
)
.
end
;
}
else
{
end
=
start
;
}
return
range
(
start
,
end
start
)
;
}
tokens
.
push
(
token
)
;
}
// reached the end of tokens list
if
(
tokens
.
length
)
{
return
range
(
tokens
[
0
]
.
start
,
_
.
last
(
tokens
)
.
end
tokens
[
0
]
.
start
)
;
}
}

Helper function that searches for CSS property value range next to
iterator's current position
@param {TokenIterator} it
@returns {Range}

Helper function that searches for CSS property value range next to iterator s current position


function findParts(str) {
		/** @type StringStream */
		var stream = require('stringStream').create(str);
		var ch;
		var result = [];
		var sep = /[\s\u00a0,]/;

		var add = function() {
			stream.next();
			result.push(range(stream.start, stream.current()));
			stream.start = stream.pos;
		};

		// skip whitespace
		stream.eatSpace();
		stream.start = stream.pos;

		while (ch = stream.next()) {
			if (ch == '"' || ch == "'") {
				stream.next();
				if (!stream.skipTo(ch)) break;
				add();
			} else if (ch == '(') {
				// function found, may have nested function
				stream.backUp(1);
				if (!stream.skipToPair('(', ')')) break;
				stream.backUp(1);
				add();
			} else {
				if (sep.test(ch)) {
					result.push(range(stream.start, stream.current().length - 1));
					stream.eatWhile(sep);
					stream.start = stream.pos;
				}
			}
		}

		add();

		return _.chain(result)
			.filter(function(item) {
				return !!item.length();
			})
			.uniq(false, function(item) {
				return item.toString();
			})
			.value();
	}

Finds parts of complex CSS value
@param {String} str
@returns {Array} Returns list of Range's


function(isAbsolute) {
			var parts = findParts(this.value());
			if (isAbsolute) {
				var offset = this.valuePosition(true);
				_.each(parts, function(p) {
					p.shift(offset);
				});
			}

			return parts;
		}

function ( isAbsolute ) { var parts = findParts ( this . value ( ) ) ; if ( isAbsolute ) { var offset = this . valuePosition ( true ) ; _ . each ( parts , function ( p ) { p . shift ( offset ) ; } ) ; } return parts ; }

Returns ranges of complex value parts
@returns {Array} Returns null if value is not complex


function(content, pos, isBackward) {
			var result = '';
			var len = content.length;
			var offset = pos;
			var stopChars = '{}/\\<>\n\r';
			var bracePos = -1, ch;

			// search left until we find rule edge
			while (offset >= 0) {
				ch = content.charAt(offset);
				if (ch == '{') {
					bracePos = offset;
					break;
				}
				else if (ch == '}' && !isBackward) {
					offset++;
					break;
				}

				offset--;
			}

			// search right for full rule set
			while (offset < len) {
				ch = content.charAt(offset);
				if (ch == '{') {
					bracePos = offset;
				} else if (ch == '}') {
					if (bracePos != -1)
						result = content.substring(bracePos, offset + 1);
					break;
				}

				offset++;
			}

			if (result) {
				// find CSS selector
				offset = bracePos - 1;
				var selector = '';
				while (offset >= 0) {
					ch = content.charAt(offset);
					if (stopChars.indexOf(ch) != -1) break;
					offset--;
				}

				// also trim whitespace
				selector = content.substring(offset + 1, bracePos).replace(/^[\s\n\r]+/m, '');
				return require('range').create(bracePos - selector.length, result.length + selector.length);
			}

			return null;
		}

function
(
content
,
pos
,
isBackward
)
{
var
result
=
''
;
var
len
=
content
.
length
;
var
offset
=
pos
;
var
stopChars
=
'{}/\\<>\n\r'
;
var
bracePos
=
1
,
ch
;
// search left until we find rule edge
while
(
offset
>=
0
)
{
ch
=
content
.
charAt
(
offset
)
;
if
(
ch
==
'{'
)
{
bracePos
=
offset
;
break
;
}
else
if
(
ch
==
'}'
&&
!
isBackward
)
{
offset
++
;
break
;
}
offset
;
}
// search right for full rule set
while
(
offset
<
len
)
{
ch
=
content
.
charAt
(
offset
)
;
if
(
ch
==
'{'
)
{
bracePos
=
offset
;
}
else
if
(
ch
==
'}'
)
{
if
(
bracePos
!=
1
)
result
=
content
.
substring
(
bracePos
,
offset
+
1
)
;
break
;
}
offset
++
;
}
if
(
result
)
{
// find CSS selector
offset
=
bracePos
1
;
var
selector
=
''
;
while
(
offset
>=
0
)
{
ch
=
content
.
charAt
(
offset
)
;
if
(
stopChars
.
indexOf
(
ch
)
!=
1
)
break
;
offset
;
}
// also trim whitespace
selector
=
content
.
substring
(
offset
+
1
,
bracePos
)
.
replace
(
/
^[\s\n\r]+
/
m
,
''
)
;
return
require
(
'range'
)
.
create
(
bracePos
selector
.
length
,
result
.
length
+
selector
.
length
)
;
}
return
;
}

Extracts single CSS selector definition from source code
@param {String} content CSS source code
@param {Number} pos Character position where to start source code extraction
@returns {Range}


function(name, value, pos) {
			var list = this.list();
			var start = this.nameRange().end;
			var editTree = require('editTree');
			var styles = _.pick(this.options, 'styleBefore', 'styleSeparator', 'styleQuote');

			if (_.isUndefined(pos))
				pos = list.length;

			/** @type XMLEditAttribute */
			var donor = list[pos];
			if (donor) {
				start = donor.fullRange().start;
			} else if (donor = list[pos - 1]) {
				start = donor.range().end;
			}

			if (donor) {
				styles = _.pick(donor, 'styleBefore', 'styleSeparator', 'styleQuote');
			}

			value = styles.styleQuote + value + styles.styleQuote;

			var attribute = new XMLEditElement(this,
					editTree.createToken(start + styles.styleBefore.length, name),
					editTree.createToken(start + styles.styleBefore.length + name.length
							+ styles.styleSeparator.length, value)
					);

			_.extend(attribute, styles);

			// write new attribute into the source
			this._updateSource(attribute.styleBefore + attribute.toString(), start);

			// insert new attribute
			this._children.splice(pos, 0, attribute);
			return attribute;
		}

function
(
name
,
value
,
pos
)
{
var
list
=
this
.
list
(
)
;
var
start
=
this
.
nameRange
(
)
.
end
;
var
editTree
=
require
(
'editTree'
)
;
var
styles
=
_
.
pick
(
this
.
options
,
'styleBefore'
,
'styleSeparator'
,
'styleQuote'
)
;
if
(
_
.
isUndefined
(
pos
)
)
pos
=
list
.
length
;
/** @type XMLEditAttribute */
var
donor
=
list
[
pos
]
;
if
(
donor
)
{
start
=
donor
.
fullRange
(
)
.
start
;
}
else
if
(
donor
=
list
[
pos
1
]
)
{
start
=
donor
.
range
(
)
.
end
;
}
if
(
donor
)
{
styles
=
_
.
pick
(
donor
,
'styleBefore'
,
'styleSeparator'
,
'styleQuote'
)
;
}
value
=
styles
.
styleQuote
+
value
+
styles
.
styleQuote
;
var
attribute
=
new
XMLEditElement
(
this
,
editTree
.
createToken
(
start
+
styles
.
styleBefore
.
length
,
name
)
,
editTree
.
createToken
(
start
+
styles
.
styleBefore
.
length
+
name
.
length
+
styles
.
styleSeparator
.
length
,
value
)
)
;
_
.
extend
(
attribute
,
styles
)
;
// write new attribute into the source
this
.
_updateSource
(
attribute
.
styleBefore
+
attribute
.
toString
(
)
,
start
)
;
// insert new attribute
this
.
_children
.
splice
(
pos
,
0
,
attribute
)
;
return
attribute
;
}

Adds new attribute
@param {String} name Property name
@param {String} value Property value
@param {Number} pos Position at which to insert new property. By
default the property is inserted at the end of rule


function(editor) {
			/** @type Range */
			var range = require('range').create(editor.getSelectionRange());
			var content = String(editor.getContent());
			if (range.length()) {
				// abbreviation is selected by user
				return range.substring(content);
			}

			// search for new abbreviation from current caret position
			var curLine = editor.getCurrentLineRange();
			return require('actionUtils').extractAbbreviation(content.substring(curLine.start, range.start));
		}

Search for abbreviation in editor from current caret position
@param {IEmmetEditor} editor Editor instance
@return {String}


function(abbr, text, syntax, profile, contextNode) {
			/** @type emmet.filters */
			var filters = require('filters');
			/** @type emmet.utils */
			var utils = require('utils');

			syntax = syntax || emmet.defaultSyntax();
			profile = require('profile').get(profile, syntax);

			require('tabStops').resetTabstopIndex();

			var data = filters.extractFromAbbreviation(abbr);
			var parsedTree = require('abbreviationParser').parse(data[0], {
				syntax: syntax,
				pastedContent: text,
				contextNode: contextNode
			});
			if (parsedTree) {
				var filtersList = filters.composeList(syntax, profile, data[1]);
				filters.apply(parsedTree, filtersList, profile);
				return utils.replaceVariables(parsedTree.toString());
			}

			return null;
		}

function
(
abbr
,
text
,
syntax
,
profile
,
contextNode
)
{
/** @type emmet.filters */
var
filters
=
require
(
'filters'
)
;
/** @type emmet.utils */
var
utils
=
require
(
'utils'
)
;
syntax
=
syntax
||
emmet
.
defaultSyntax
(
)
;
profile
=
require
(
'profile'
)
.
get
(
profile
,
syntax
)
;
require
(
'tabStops'
)
.
resetTabstopIndex
(
)
;
var
data
=
filters
.
extractFromAbbreviation
(
abbr
)
;
var
parsedTree
=
require
(
'abbreviationParser'
)
.
parse
(
data
[
0
]
,
{
syntax
:
syntax
,
pastedContent
:
text
,
contextNode
:
contextNode
}
)
;
if
(
parsedTree
)
{
var
filtersList
=
filters
.
composeList
(
syntax
,
profile
,
data
[
1
]
)
;
filters
.
apply
(
parsedTree
,
filtersList
,
profile
)
;
return
utils
.
replaceVariables
(
parsedTree
.
toString
(
)
)
;
}
return
;
}

Wraps passed text with abbreviation. Text will be placed inside last
expanded element
@memberOf wrapWithAbbreviation
@param {String} abbr Abbreviation
@param {String} text Text to wrap
@param {String} syntax Document type (html, xml, etc.). Default is 'html'
@param {String} profile Output profile's name. Default is 'plain'
@param {Object} contextNode Context node inside which abbreviation
is wrapped. It will be used as a reference for node name resolvers
@return {String}

Wraps passed text with abbreviation . Text will be placed inside last expanded element


function toggleHTMLComment(editor) {
		/** @type Range */
		var range = require('range').create(editor.getSelectionRange());
		var info = require('editorUtils').outputInfo(editor);

		if (!range.length()) {
			// no selection, find matching tag
			var tag = require('htmlMatcher').tag(info.content, editor.getCaretPos());
			if (tag) { // found pair
				range = tag.outerRange;
			}
		}

		return genericCommentToggle(editor, '<!--', '-->', range);
	}

function
toggleHTMLComment
(
editor
)
{
/** @type Range */
var
range
=
require
(
'range'
)
.
create
(
editor
.
getSelectionRange
(
)
)
;
var
info
=
require
(
'editorUtils'
)
.
outputInfo
(
editor
)
;
if
(
!
range
.
length
(
)
)
{
// no selection, find matching tag
var
tag
=
require
(
'htmlMatcher'
)
.
tag
(
info
.
content
,
editor
.
getCaretPos
(
)
)
;
if
(
tag
)
{
// found pair
range
=
tag
.
outerRange
;
}
}
return
genericCommentToggle
(
editor
,
'<!--'
,
'-->'
,
range
)
;
}

Toggle HTML comment on current selection or tag
@param {IEmmetEditor} editor
@return {Boolean} Returns true if comment was toggled


function toggleCSSComment(editor) {
		/** @type Range */
		var range = require('range').create(editor.getSelectionRange());
		var info = require('editorUtils').outputInfo(editor);

		if (!range.length()) {
			// no selection, try to get current rule
			/** @type CSSRule */
			var rule = require('cssEditTree').parseFromPosition(info.content, editor.getCaretPos());
			if (rule) {
				var property = cssItemFromPosition(rule, editor.getCaretPos());
				range = property
					? property.range(true)
					: require('range').create(rule.nameRange(true).start, rule.source);
			}
		}

		if (!range.length()) {
			// still no selection, get current line
			range = require('range').create(editor.getCurrentLineRange());
			require('utils').narrowToNonSpace(info.content, range);
		}

		return genericCommentToggle(editor, '/*', '*/', range);
	}

Simple CSS commenting
@param {IEmmetEditor} editor
@return {Boolean} Returns true if comment was toggled


function genericCommentToggle(editor, commentStart, commentEnd, range) {
		var editorUtils = require('editorUtils');
		var content = editorUtils.outputInfo(editor).content;
		var caretPos = editor.getCaretPos();
		var newContent = null;

		var utils = require('utils');

		/**
		 * Remove comment markers from string
		 * @param {Sting} str
		 * @return {String}
		 */
		function removeComment(str) {
			return str
				.replace(new RegExp('^' + utils.escapeForRegexp(commentStart) + '\\s*'), function(str){
					caretPos -= str.length;
					return '';
				}).replace(new RegExp('\\s*' + utils.escapeForRegexp(commentEnd) + '$'), '');
		}

		// first, we need to make sure that this substring is not inside
		// comment
		var commentRange = searchComment(content, caretPos, commentStart, commentEnd);
		if (commentRange && commentRange.overlap(range)) {
			// we're inside comment, remove it
			range = commentRange;
			newContent = removeComment(range.substring(content));
		} else {
			// should add comment
			// make sure that there's no comment inside selection
			newContent = commentStart + ' ' +
				range.substring(content)
					.replace(new RegExp(utils.escapeForRegexp(commentStart) + '\\s*|\\s*' + utils.escapeForRegexp(commentEnd), 'g'), '') +
				' ' + commentEnd;

			// adjust caret position
			caretPos += commentStart.length + 1;
		}

		// replace editor content
		if (newContent !== null) {
			newContent = utils.escapeText(newContent);
			editor.setCaretPos(range.start);
			editor.replaceContent(editorUtils.unindent(editor, newContent), range.start, range.end);
			editor.setCaretPos(caretPos);
			return true;
		}

		return false;
	}

Generic comment toggling routine
@param {IEmmetEditor} editor
@param {String} commentStart Comment start token
@param {String} commentEnd Comment end token
@param {Range} range Selection range
@return {Boolean}


function removeComment(str) {
			return str
				.replace(new RegExp('^' + utils.escapeForRegexp(commentStart) + '\\s*'), function(str){
					caretPos -= str.length;
					return '';
				}).replace(new RegExp('\\s*' + utils.escapeForRegexp(commentEnd) + '$'), '');
		}

function removeComment ( str ) { return str . replace ( new RegExp ( '^' + utils . escapeForRegexp ( commentStart ) + '\\s*' ) , function ( str ) { caretPos -= str . length ; return '' ; } ) . replace ( new RegExp ( '\\s*' + utils . escapeForRegexp ( commentEnd ) + '$' ) , '' ) ; }

Remove comment markers from string
@param {Sting} str
@return {String}


function makePossibleRangesHTML(source, tokens, offset) {
		offset = offset || 0;
		var range = require('range');
		var result = [];
		var attrStart = -1, attrName = '', attrValue = '', attrValueRange, tagName;
		_.each(tokens, function(tok) {
			switch (tok.type) {
				case 'tag':
					tagName = source.substring(tok.start, tok.end);
					if (/^<[\w\:\-]/.test(tagName)) {
						// add tag name
						result.push(range.create({
							start: tok.start + 1,
							end: tok.end
						}));
					}
					break;
				case 'attribute':
					attrStart = tok.start;
					attrName = source.substring(tok.start, tok.end);
					break;

				case 'string':
					// attribute value
					// push full attribute first
					 result.push(range.create(attrStart, tok.end - attrStart));

					 attrValueRange = range.create(tok);
					 attrValue = attrValueRange.substring(source);

					 // is this a quoted attribute?
					 if (isQuote(attrValue.charAt(0)))
						 attrValueRange.start++;

					 if (isQuote(attrValue.charAt(attrValue.length - 1)))
						 attrValueRange.end--;

					 result.push(attrValueRange);

					 if (attrName == 'class') {
						 result = result.concat(classNameRanges(attrValueRange.substring(source), attrValueRange.start));
					 }

					break;
			}
		});

		// offset ranges
		_.each(result, function(r) {
			r.shift(offset);
		});

		return _.chain(result)
			.filter(function(item) {        // remove empty
				return !!item.length();
			})
			.uniq(false, function(item) {   // remove duplicates
				return item.toString();
			})
			.value();
	}

function
makePossibleRangesHTML
(
source
,
tokens
,
offset
)
{
offset
=
offset
||
0
;
var
range
=
require
(
'range'
)
;
var
result
=
[
]
;
var
attrStart
=
1
,
attrName
=
''
,
attrValue
=
''
,
attrValueRange
,
tagName
;
_
.
each
(
tokens
,
function
(
tok
)
{
switch
(
tok
.
type
)
{
case
'tag'
:
tagName
=
source
.
substring
(
tok
.
start
,
tok
.
end
)
;
if
(
/
^<[\w\:\-]
/
.
test
(
tagName
)
)
{
// add tag name
result
.
push
(
range
.
create
(
{
start
:
tok
.
start
+
1
,
end
:
tok
.
end
}
)
)
;
}
break
;
case
'attribute'
:
attrStart
=
tok
.
start
;
attrName
=
source
.
substring
(
tok
.
start
,
tok
.
end
)
;
break
;
case
'string'
:
// attribute value
// push full attribute first
result
.
push
(
range
.
create
(
attrStart
,
tok
.
end
attrStart
)
)
;
attrValueRange
=
range
.
create
(
tok
)
;
attrValue
=
attrValueRange
.
substring
(
source
)
;
// is this a quoted attribute?
if
(
isQuote
(
attrValue
.
charAt
(
0
)
)
)
attrValueRange
.
start
++
;
if
(
isQuote
(
attrValue
.
charAt
(
attrValue
.
length
1
)
)
)
attrValueRange
.
end
;
result
.
push
(
attrValueRange
)
;
if
(
attrName
==
'class'
)
{
result
=
result
.
concat
(
classNameRanges
(
attrValueRange
.
substring
(
source
)
,
attrValueRange
.
start
)
)
;
}
break
;
}
}
)
;
// offset ranges
_
.
each
(
result
,
function
(
r
)
{
r
.
shift
(
offset
)
;
}
)
;
return
_
.
chain
(
result
)
.
filter
(
function
(
item
)
{
// remove empty
return
!
!
item
.
length
(
)
;
}
)
.
uniq
(
false
,
function
(
item
)
{
// remove duplicates
return
item
.
toString
(
)
;
}
)
.
value
(
)
;
}

Creates possible selection ranges for HTML tag
@param {String} source Original HTML source for tokens
@param {Array} tokens List of HTML tokens
@returns {Array}


function classNameRanges(className, offset) {
		offset = offset || 0;
		var result = [];
		/** @type StringStream */
		var stream = require('stringStream').create(className);
		var range = require('range');

		// skip whitespace
		stream.eatSpace();
		stream.start = stream.pos;

		var ch;
		while (ch = stream.next()) {
			if (/[\s\u00a0]/.test(ch)) {
				result.push(range.create(stream.start + offset, stream.pos - stream.start - 1));
				stream.eatSpace();
				stream.start = stream.pos;
			}
		}

		result.push(range.create(stream.start + offset, stream.pos - stream.start));
		return result;
	}

function
classNameRanges
(
className
,
offset
)
{
offset
=
offset
||
0
;
var
result
=
[
]
;
/** @type StringStream */
var
stream
=
require
(
'stringStream'
)
.
create
(
className
)
;
var
range
=
require
(
'range'
)
;
// skip whitespace
stream
.
eatSpace
(
)
;
stream
.
start
=
stream
.
pos
;
var
ch
;
while
(
ch
=
stream
.
next
(
)
)
{
if
(
/
[\s\u00a0]
/
.
test
(
ch
)
)
{
result
.
push
(
range
.
create
(
stream
.
start
+
offset
,
stream
.
pos
stream
.
start
1
)
)
;
stream
.
eatSpace
(
)
;
stream
.
start
=
stream
.
pos
;
}
}
result
.
push
(
range
.
create
(
stream
.
start
+
offset
,
stream
.
pos
stream
.
start
)
)
;
return
result
;
}

Returns ranges of class names in "class" attribute value
@param {String} className
@returns {Array}

Returns ranges of class names in class attribute value


function makePossibleRangesCSS(property) {
		// find all possible ranges, sorted by position and size
		var valueRange = property.valueRange(true);
		var result = [property.range(true), valueRange];
		var stringStream = require('stringStream');
		var cssEditTree = require('cssEditTree');
		var range = require('range');

		// locate parts of complex values.
		// some examples:
		// – 1px solid red: 3 parts
		// – arial, sans-serif: enumeration, 2 parts
		// – url(image.png): function value part
		var value = property.value();
		_.each(property.valueParts(), function(r) {
			// add absolute range
			var clone = r.clone();
			result.push(clone.shift(valueRange.start));

			/** @type StringStream */
			var stream = stringStream.create(r.substring(value));
			if (stream.match(/^[\w\-]+\(/, true)) {
				// we have a function, find values in it.
				// but first add function contents
				stream.start = stream.pos;
				stream.skipToPair('(', ')');
				var fnBody = stream.current();
				result.push(range.create(clone.start + stream.start, fnBody));

				// find parts
				_.each(cssEditTree.findParts(fnBody), function(part) {
					result.push(range.create(clone.start + stream.start + part.start, part.substring(fnBody)));
				});
			}
		});

		// optimize result: remove empty ranges and duplicates
		return _.chain(result)
			.filter(function(item) {
				return !!item.length();
			})
			.uniq(false, function(item) {
				return item.toString();
			})
			.value();
	}

Makes all possible selection ranges for specified CSS property
@param {CSSProperty} property
@returns {Array}


function matchedRangeForCSSProperty(rule, selRange, isBackward) {
		/** @type CSSProperty */
		var property = null;
		var possibleRanges, curRange = null, ix;
		var list = rule.list();
		var searchFn, nearestItemFn;

		if (isBackward) {
			list.reverse();
			searchFn = function(p) {
				return p.range(true).start <= selRange.start;
			};
			nearestItemFn = function(r) {
				return r.start < selRange.start;
			};
		} else {
			searchFn = function(p) {
				return p.range(true).end >= selRange.end;
			};
			nearestItemFn = function(r) {
				return r.end > selRange.start;
			};
		}

		// search for nearest to selection CSS property
		while (property = _.find(list, searchFn)) {
			possibleRanges = makePossibleRangesCSS(property);
			if (isBackward)
				possibleRanges.reverse();

			// check if any possible range is already selected
			curRange = _.find(possibleRanges, function(r) {
				return r.equal(selRange);
			});

			if (!curRange) {
				// no selection, select nearest item
				var matchedRanges = _.filter(possibleRanges, function(r) {
					return r.inside(selRange.end);
				});

				if (matchedRanges.length > 1) {
					curRange = matchedRanges[1];
					break;
				}

				if (curRange = _.find(possibleRanges, nearestItemFn))
					break;
			} else {
				ix = _.indexOf(possibleRanges, curRange);
				if (ix != possibleRanges.length - 1) {
					curRange = possibleRanges[ix + 1];
					break;
				}
			}

			curRange = null;
			selRange.start = selRange.end = isBackward
				? property.range(true).start - 1
				: property.range(true).end + 1;
		}

		return curRange;
	}

Tries to find matched CSS property and nearest range for selection
@param {CSSRule} rule
@param {Range} selRange
@param {Boolean} isBackward
@returns {Range}


function matchPair(editor, direction) {
		direction = String((direction || 'out').toLowerCase());
		var info = require('editorUtils').outputInfo(editor);

		var range = require('range');
		/** @type Range */
		var sel = range.create(editor.getSelectionRange());
		var content = info.content;

		// validate previous match
		if (lastMatch && !lastMatch.range.equal(sel)) {
			lastMatch = null;
		}

		if (lastMatch && sel.length()) {
			if (direction == 'in') {
				// user has previously selected tag and wants to move inward
				if (lastMatch.type == 'tag' && !lastMatch.close) {
					// unary tag was selected, can't move inward
					return false;
				} else {
					if (lastMatch.range.equal(lastMatch.outerRange)) {
						lastMatch.range = lastMatch.innerRange;
					} else {
						var narrowed = require('utils').narrowToNonSpace(content, lastMatch.innerRange);
						lastMatch = matcher.find(content, narrowed.start + 1);
						if (lastMatch && lastMatch.range.equal(sel) && lastMatch.outerRange.equal(sel)) {
							lastMatch.range = lastMatch.innerRange;
						}
					}
				}
			} else {
				if (
						!lastMatch.innerRange.equal(lastMatch.outerRange)
						&& lastMatch.range.equal(lastMatch.innerRange)
						&& sel.equal(lastMatch.range)) {
					lastMatch.range = lastMatch.outerRange;
				} else {
					lastMatch = matcher.find(content, sel.start);
					if (lastMatch && lastMatch.range.equal(sel) && lastMatch.innerRange.equal(sel)) {
						lastMatch.range = lastMatch.outerRange;
					}
				}
			}
		} else {
			lastMatch = matcher.find(content, sel.start);
		}

		if (lastMatch && !lastMatch.range.equal(sel)) {
			editor.createSelection(lastMatch.range.start, lastMatch.range.end);
			return true;
		}

		lastMatch = null;
		return false;
	}

Find and select HTML tag pair
@param {IEmmetEditor} editor Editor instance
@param {String} direction Direction of pair matching: 'in' or 'out'.
Default is 'out'


function getReflectedCSSName(name) {
		name = require('cssEditTree').baseName(name);
		var vendorPrefix = '^(?:\\-\\w+\\-)?', m;

		if (name == 'opacity' || name == 'filter') {
			return new RegExp(vendorPrefix + '(?:opacity|filter)$');
		} else if (m = name.match(/^border-radius-(top|bottom)(left|right)/)) {
			// Mozilla-style border radius
			return new RegExp(vendorPrefix + '(?:' + name + '|border-' + m[1] + '-' + m[2] + '-radius)$');
		} else if (m = name.match(/^border-(top|bottom)-(left|right)-radius/)) {
			return new RegExp(vendorPrefix + '(?:' + name + '|border-radius-' + m[1] + m[2] + ')$');
		}

		return new RegExp(vendorPrefix + name + '$');
	}

Returns regexp that should match reflected CSS property names
@param {String} name Current CSS property name
@return {RegExp}


function decodeFromBase64(editor, data, pos) {
		// ask user to enter path to file
		var filePath = String(editor.prompt('Enter path to file (absolute or relative)'));
		if (!filePath)
			return false;

		var file = require('file');
		var absPath = file.createPath(editor.getFilePath(), filePath);
		if (!absPath) {
			throw "Can't save file";
		}

		file.save(absPath, require('base64').decode( data.replace(/^data\:.+?;.+?,/, '') ));
		editor.replaceContent('$0' + filePath, pos, pos + data.length);
		return true;
	}

Decodes base64 string back to file.
@param {IEmmetEditor} editor
@param {String} data Base64-encoded file content
@param {Number} pos Caret position where image is located in the editor


function updateImageSizeCSS(editor) {
		var offset = editor.getCaretPos();

		// find tag from current caret position
		var info = require('editorUtils').outputInfo(editor);
		var cssRule = require('cssEditTree').parseFromPosition(info.content, offset, true);
		if (cssRule) {
			// check if there is property with image under caret
			var prop = cssRule.itemFromPosition(offset, true), m;
			if (prop && (m = /url\((["']?)(.+?)\1\)/i.exec(prop.value() || ''))) {
				getImageSizeForSource(editor, m[2], function(size) {
					if (size) {
						var compoundData = cssRule.range(true);
						cssRule.value('width', size.width + 'px');
						cssRule.value('height', size.height + 'px', cssRule.indexOf('width') + 1);

						require('actionUtils').compoundUpdate(editor, _.extend(compoundData, {
							data: cssRule.toString(),
							caret: offset
						}));
					}
				});
			}
		}
	}

Updates image size of CSS property
@param {IEmmetEditor} editor


function getImageSizeForSource(editor, src, callback) {
		var fileContent;
		var au = require('actionUtils');
		if (src) {
			// check if it is data:url
			if (/^data:/.test(src)) {
				fileContent = require('base64').decode( src.replace(/^data\:.+?;.+?,/, '') );
				return callback(au.getImageSize(fileContent));
			}

			var file = require('file');
			var absPath = file.locateFile(editor.getFilePath(), src);
			if (absPath === null) {
				throw "Can't find " + src + ' file';
			}

			file.read(absPath, function(err, content) {
				if (err) {
					throw 'Unable to read ' + absPath + ': ' + err;
				}

				content = String(content);
				callback(au.getImageSize(content));
			});
		}
	}

function
getImageSizeForSource
(
editor
,
src
,
callback
)
{
var
fileContent
;
var
au
=
require
(
'actionUtils'
)
;
if
(
src
)
{
// check if it is data:url
if
(
/
^data:
/
.
test
(
src
)
)
{
fileContent
=
require
(
'base64'
)
.
decode
(
src
.
replace
(
/
^data\:.+?;.+?,
/
,
''
)
)
;
return
callback
(
au
.
getImageSize
(
fileContent
)
)
;
}
var
file
=
require
(
'file'
)
;
var
absPath
=
file
.
locateFile
(
editor
.
getFilePath
(
)
,
src
)
;
if
(
absPath
===
)
{
throw
"Can't find "
+
src
+
' file'
;
}
file
.
read
(
absPath
,
function
(
err
,
content
)
{
if
(
err
)
{
throw
'Unable to read '
+
absPath
+
': '
+
err
;
}
content
=
String
(
content
)
;
callback
(
au
.
getImageSize
(
content
)
)
;
}
)
;
}
}

Returns image dimensions for source
@param {IEmmetEditor} editor
@param {String} src Image source (path or data:url)


function isSingleProperty(snippet) {
		var utils = require('utils');
		snippet = utils.trim(snippet);

		// check if it doesn't contain a comment and a newline
		if (~snippet.indexOf('/*') || /[\n\r]/.test(snippet)) {
			return false;
		}

		// check if it's a valid snippet definition
		if (!/^[a-z0-9\-]+\s*\:/i.test(snippet)) {
			return false;
		}

		snippet = require('tabStops').processText(snippet, {
			replaceCarets: true,
			tabstop: function() {
				return 'value';
			}
		});

		return snippet.split(':').length == 2;
	}

Check if provided snippet contains only one CSS property and value.
@param {String} snippet
@returns {Boolean}

Check if provided snippet contains only one CSS property and value .


function normalizeValue(value) {
		if (value.charAt(0) == '-' && !/^\-[\.\d]/.test(value)) {
			value = value.replace(/^\-+/, '');
		}

		if (value.charAt(0) == '#') {
			return normalizeHexColor(value);
		}

		return getKeyword(value);
	}

function normalizeValue ( value ) { if ( value . charAt ( 0 ) == '-' && ! / ^\-[\.\d] / . test ( value ) ) { value = value . replace ( / ^\-+ / , '' ) ; } if ( value . charAt ( 0 ) == '#' ) { return normalizeHexColor ( value ) ; } return getKeyword ( value ) ; }

Normalizes abbreviated value to final CSS one
@param {String} value
@returns {String}


function findPrefixes(property, noAutofill) {
		var result = [];
		_.each(vendorPrefixes, function(obj, prefix) {
			if (hasPrefix(property, prefix)) {
				result.push(prefix);
			}
		});

		if (!result.length && !noAutofill) {
			// add all non-obsolete prefixes
			_.each(vendorPrefixes, function(obj, prefix) {
				if (!obj.obsolete)
					result.push(prefix);
			});
		}

		return result;
	}

function
findPrefixes
(
property
,
noAutofill
)
{
var
result
=
[
]
;
_
.
each
(
vendorPrefixes
,
function
(
obj
,
prefix
)
{
if
(
hasPrefix
(
property
,
prefix
)
)
{
result
.
push
(
prefix
)
;
}
}
)
;
if
(
!
result
.
length
&&
!
noAutofill
)
{
// add all non-obsolete prefixes
_
.
each
(
vendorPrefixes
,
function
(
obj
,
prefix
)
{
if
(
!
obj
.
obsolete
)
result
.
push
(
prefix
)
;
}
)
;
}
return
result
;
}

Search for a list of supported prefixes for CSS property. This list
is used to generate all-prefixed snippet
@param {String} property CSS property name
@returns {Array}

Search for a list of supported prefixes for CSS property . This list is used to generate all - prefixed snippet


function parseList(list) {
		var result = _.map((list || '').split(','), require('utils').trim);
		return result.length ? result : null;
	}

function parseList ( list ) { var result = _ . map ( ( list || '' ) . split ( ',' ) , require ( 'utils' ) . trim ) ; return result . length ? result : null ; }

Helper function that parses comma-separated list of elements into array
@param {String} list
@returns {Array}

Helper function that parses comma - separated list of elements into array


function(abbr, value, syntax) {
			syntax = syntax || 'css';
			var resources = require('resources');
			var autoInsertPrefixes = prefs.get('css.autoInsertVendorPrefixes');

			// check if snippet should be transformed to !important
			var isImportant;
			if (isImportant = /^(.+)\!$/.test(abbr)) {
				abbr = RegExp.$1;
			}

			// check if we have abbreviated resource
			var snippet = resources.findSnippet(syntax, abbr);
			if (snippet && !autoInsertPrefixes) {
				return transformSnippet(snippet, isImportant, syntax);
			}

			// no abbreviated resource, parse abbreviation
			var prefixData = this.extractPrefixes(abbr);
			var valuesData = this.extractValues(prefixData.property);
			var abbrData = _.extend(prefixData, valuesData);

			if (!snippet) {
				snippet = resources.findSnippet(syntax, abbrData.property);
			} else {
				abbrData.values = null;
			}

			if (!snippet && prefs.get('css.fuzzySearch')) {
				// let’s try fuzzy search
				snippet = resources.fuzzyFindSnippet(syntax, abbrData.property, parseFloat(prefs.get('css.fuzzySearchMinScore')));
			}

			if (!snippet) {
				snippet = abbrData.property + ':' + defaultValue;
			} else if (!_.isString(snippet)) {
				snippet = snippet.data;
			}

			if (!isSingleProperty(snippet)) {
				return snippet;
			}

			var snippetObj = this.splitSnippet(snippet);
			var result = [];
			if (!value && abbrData.values) {
				value = _.map(abbrData.values, function(val) {
					return this.normalizeValue(val, snippetObj.name);
				}, this).join(' ') + ';';
			}

			snippetObj.value = value || snippetObj.value;

			var prefixes = abbrData.prefixes == 'all' || (!abbrData.prefixes && autoInsertPrefixes)
				? findPrefixes(snippetObj.name, autoInsertPrefixes && abbrData.prefixes != 'all')
				: abbrData.prefixes;

			var names = [], propName;
			_.each(prefixes, function(p) {
				if (p in vendorPrefixes) {
					propName = vendorPrefixes[p].transformName(snippetObj.name);
					names.push(propName);
					result.push(transformSnippet(propName + ':' + snippetObj.value,
							isImportant, syntax));
				}
			});

			// put the original property
			result.push(transformSnippet(snippetObj.name + ':' + snippetObj.value, isImportant, syntax));
			names.push(snippetObj.name);

			if (prefs.get('css.alignVendor')) {
				var pads = require('utils').getStringsPads(names);
				result = _.map(result, function(prop, i) {
					return pads[i] + prop;
				});
			}

			return result;
		}

Expands abbreviation into a snippet
@param {String} abbr Abbreviation name to expand
@param {String} value Abbreviation value
@param {String} syntax Currect syntax or dialect. Default is 'css'
@returns {Object} Array of CSS properties and values or predefined
snippet (string or element)


function(snippet) {
			var utils = require('utils');
			snippet = utils.trim(snippet);
			if (snippet.indexOf(':') == -1) {
				return {
					name: snippet,
					value: defaultValue
				};
			}

			var pair = snippet.split(':');

			return {
				name: utils.trim(pair.shift()),
				// replace ${0} tabstop to produce valid vendor-prefixed values
				// where possible
				value: utils.trim(pair.join(':')).replace(/^(\$\{0\}|\$0)(\s*;?)$/, '${1}$2')
			};
		}

Split snippet into a CSS property-value pair
@param {String} snippet

Split snippet into a CSS property - value pair


function parseLinearGradient(gradient) {
		var direction = defaultLinearDirections[0];

		// extract tokens
		/** @type StringStream */
		var stream = require('stringStream').create(require('utils').trim(gradient));
		var colorStops = [], ch;
		while (ch = stream.next()) {
			if (stream.peek() == ',') {
				colorStops.push(stream.current());
				stream.next();
				stream.eatSpace();
				stream.start = stream.pos;
			} else if (ch == '(') { // color definition, like 'rgb(0,0,0)'
				stream.skipTo(')');
			}
		}

		// add last token
		colorStops.push(stream.current());
		colorStops = _.compact(_.map(colorStops, normalizeSpace));

		if (!colorStops.length)
			return null;

		// let's see if the first color stop is actually a direction
		if (reDeg.test(colorStops[0]) || reKeyword.test(colorStops[0])) {
			direction = colorStops.shift();
		}

		return {
			type: 'linear',
			direction: direction,
			colorStops: _.map(colorStops, parseColorStop)
		};
	}

Parses linear gradient definition
@param {String}


function fillImpliedPositions(colorStops) {
		var from = 0;

		_.each(colorStops, function(cs, i) {
			// make sure that first and last positions are defined
			if (!i)
				return cs.position = cs.position || 0;

			if (i == colorStops.length - 1 && !('position' in cs))
				cs.position = 1;

			if ('position' in cs) {
				var start = colorStops[from].position || 0;
				var step = (cs.position - start) / (i - from);
				_.each(colorStops.slice(from, i), function(cs2, j) {
					cs2.position = start + step * j;
				});

				from = i;
			}
		});
	}

Fills-out implied positions in color-stops. This function is useful for
old Webkit gradient definitions

Fills - out implied positions in color - stops . This function is useful for old Webkit gradient definitions


function textualDirection(direction) {
		var angle = parseFloat(direction);

		if(!_.isNaN(angle)) {
			switch(angle % 360) {
				case 0:   return 'left';
				case 90:  return 'bottom';
				case 180: return 'right';
				case 240: return 'top';
			}
		}

		return direction;
	}

function textualDirection ( direction ) { var angle = parseFloat ( direction ) ; if ( ! _ . isNaN ( angle ) ) { switch ( angle % 360 ) { case 0 : return 'left' ; case 90 : return 'bottom' ; case 180 : return 'right' ; case 240 : return 'top' ; } } return direction ; }

Returns textual version of direction expressed in degrees
@param {String} direction
@returns {String}


function getPropertiesForGradient(gradient, propertyName) {
		var props = [];
		var css = require('cssResolver');

		if (prefs.get('css.gradient.fallback') && ~propertyName.toLowerCase().indexOf('background')) {
			props.push({
				name: 'background-color',
				value: '${1:' + gradient.colorStops[0].color + '}'
			});
		}

		_.each(prefs.getArray('css.gradient.prefixes'), function(prefix) {
			var name = css.prefixed(propertyName, prefix);
			if (prefix == 'webkit' && prefs.get('css.gradient.oldWebkit')) {
				try {
					props.push({
						name: name,
						value: module.oldWebkitLinearGradient(gradient)
					});
				} catch(e) {}
			}

			props.push({
				name: name,
				value: module.toString(gradient, prefix)
			});
		});

		return props.sort(function(a, b) {
			return b.name.length - a.name.length;
		});
	}

function getPropertiesForGradient ( gradient , propertyName ) { var props = [ ] ; var css = require ( 'cssResolver' ) ; if ( prefs . get ( 'css.gradient.fallback' ) && ~ propertyName . toLowerCase ( ) . indexOf ( 'background' ) ) { props . push ( { name : 'background-color' , value : '${1:' + gradient . colorStops [ 0 ] . color + '}' } ) ; } _ . each ( prefs . getArray ( 'css.gradient.prefixes' ) , function ( prefix ) { var name = css . prefixed ( propertyName , prefix ) ; if ( prefix == 'webkit' && prefs . get ( 'css.gradient.oldWebkit' ) ) { try { props . push ( { name : name , value : module . oldWebkitLinearGradient ( gradient ) } ) ; } catch ( e ) { } } props . push ( { name : name , value : module . toString ( gradient , prefix ) } ) ; } ) ; return props . sort ( function ( a , b ) { return b . name . length - a . name . length ; } ) ; }

Returns list of CSS properties with gradient
@param {Object} gradient
@param {String} propertyName Original CSS property name
@returns {Array}


function findGradient(cssProp) {
		var value = cssProp.value();
		var gradient = null;
		var matchedPart = _.find(cssProp.valueParts(), function(part) {
			return gradient = module.parse(part.substring(value));
		});

		if (matchedPart && gradient) {
			return {
				gradient: gradient,
				valueRange: matchedPart
			};
		}

		return null;
	}

function findGradient ( cssProp ) { var value = cssProp . value ( ) ; var gradient = null ; var matchedPart = _ . find ( cssProp . valueParts ( ) , function ( part ) { return gradient = module . parse ( part . substring ( value ) ) ; } ) ; if ( matchedPart && gradient ) { return { gradient : gradient , valueRange : matchedPart } ; } return null ; }

Search for gradient definition inside CSS property value


function expandGradientOutsideValue(editor, syntax) {
		var propertyName = prefs.get('css.gradient.defaultProperty');

		if (!propertyName)
			return false;

		// assuming that gradient definition is written on new line,
		// do a simplified parsing
		var content = String(editor.getContent());
		/** @type Range */
		var lineRange = require('range').create(editor.getCurrentLineRange());

		// get line content and adjust range with padding
		var line = lineRange.substring(content)
			.replace(/^\s+/, function(pad) {
				lineRange.start += pad.length;
				return '';
			})
			.replace(/\s+$/, function(pad) {
				lineRange.end -= pad.length;
				return '';
			});

		var css = require('cssResolver');
		var gradient = module.parse(line);
		if (gradient) {
			var props = getPropertiesForGradient(gradient, propertyName);
			props.push({
				name: propertyName,
				value: module.toString(gradient) + '${2}'
			});

			var sep = css.getSyntaxPreference('valueSeparator', syntax);
			var end = css.getSyntaxPreference('propertyEnd', syntax);

			if (require('preferences').get('css.alignVendor')) {
				var pads = require('utils').getStringsPads(_.map(props, function(prop) {
					return prop.value.substring(0, prop.value.indexOf('('));
				}));
				_.each(props, function(prop, i) {
					prop.value = pads[i] + prop.value;
				});
			}

			props = _.map(props, function(item) {
				return item.name + sep + item.value + end;
			});

			editor.replaceContent(props.join('\n'), lineRange.start, lineRange.end);
			return true;
		}

		return false;
	}

Tries to expand gradient outside CSS value
@param {IEmmetEditor} editor
@param {String} syntax


function findGradientFromPosition(content, pos) {
		var cssProp = null;
		/** @type EditContainer */
		var cssRule = require('cssEditTree').parseFromPosition(content, pos, true);

		if (cssRule) {
			cssProp = cssRule.itemFromPosition(pos, true);
			if (!cssProp) {
				// in case user just started writing CSS property
				// and didn't include semicolon–try another approach
				cssProp = _.find(cssRule.list(), function(elem) {
					return elem.range(true).end == pos;
				});
			}
		}

		return {
			rule: cssRule,
			property: cssProp
		};
	}

Search for gradient definition inside CSS value under cursor
@param {String} content
@param {Number} pos
@returns {Object}


function(gradient) {
			var result = null;
			require('utils').trim(gradient).replace(/^([\w\-]+)\((.+?)\)$/, function(str, type, definition) {
				// remove vendor prefix
				type = type.toLowerCase().replace(/^\-[a-z]+\-/, '');
				if (type == 'linear-gradient' || type == 'lg') {
					result = parseLinearGradient(definition);
					return '';
				}

				return str;
			});

			return result;
		}

function
(
gradient
)
{
var
result
=
;
require
(
'utils'
)
.
trim
(
gradient
)
.
replace
(
/
^([\w\-]+)\((.+?)\)$
/
,
function
(
str
,
type
,
definition
)
{
// remove vendor prefix
type
=
type
.
toLowerCase
(
)
.
replace
(
/
^\-[a-z]+\-
/
,
''
)
;
if
(
type
==
'linear-gradient'
||
type
==
'lg'
)
{
result
=
parseLinearGradient
(
definition
)
;
return
''
;
}
return
str
;
}
)
;
return
result
;
}

Parses gradient definition
@param {String} gradient
@returns {Object}


function(gradient) {
			if (_.isString(gradient))
				gradient = this.parse(gradient);

			if (!gradient)
				return null;

			var colorStops = _.map(gradient.colorStops, _.clone);

			// normalize color-stops position
			_.each(colorStops, function(cs) {
				if (!('position' in cs)) // implied position
					return;

				if (~cs.position.indexOf('.') || cs.unit == '%') {
					cs.position = parseFloat(cs.position) / (cs.unit == '%' ? 100 : 1);
				} else {
					throw "Can't convert color stop '" + (cs.position + (cs.unit || '')) + "'";
				}
			});

			fillImpliedPositions(colorStops);

			// transform color-stops into string representation
			colorStops = _.map(colorStops, function(cs, i) {
				if (!cs.position && !i)
					return 'from(' + cs.color + ')';

				if (cs.position == 1 && i == colorStops.length - 1)
					return 'to(' + cs.color + ')';

				return 'color-stop(' + (cs.position.toFixed(2).replace(/\.?0+$/, '')) + ', ' + cs.color + ')';
			});

			return '-webkit-gradient(linear, '
				+ oldWebkitDirection(gradient.direction)
				+ ', '
				+ colorStops.join(', ')
				+ ')';
		}

Produces linear gradient definition used in early Webkit
implementations
@param {Object} gradient Parsed gradient
@returns {String}


function(gradient, prefix) {
			if (gradient.type == 'linear') {
				var fn = (prefix ? '-' + prefix + '-' : '') + 'linear-gradient';

				// transform color-stops
				var colorStops = _.map(gradient.colorStops, function(cs) {
					return cs.color + ('position' in cs
							? ' ' + cs.position + (cs.unit || '')
							: '');
				});

				if (gradient.direction
						&& (!prefs.get('css.gradient.omitDefaultDirection')
						|| !_.include(defaultLinearDirections, gradient.direction))) {
					colorStops.unshift(gradient.direction);
				}

				return fn + '(' + colorStops.join(', ') + ')';
			}
		}

function
(
gradient
,
prefix
)
{
if
(
gradient
.
type
==
'linear'
)
{
var
fn
=
(
prefix
'-'
+
prefix
+
'-'
:
''
)
+
'linear-gradient'
;
// transform color-stops
var
colorStops
=
_
.
map
(
gradient
.
colorStops
,
function
(
cs
)
{
return
cs
.
color
+
(
'position'
in
cs
' '
+
cs
.
position
+
(
cs
.
unit
||
''
)
:
''
)
;
}
)
;
if
(
gradient
.
direction
&&
(
!
prefs
.
get
(
'css.gradient.omitDefaultDirection'
)
||
!
_
.
include
(
defaultLinearDirections
,
gradient
.
direction
)
)
)
{
colorStops
.
unshift
(
gradient
.
direction
)
;
}
return
fn
+
'('
+
colorStops
.
join
(
', '
)
+
')'
;
}
}

Returns string representation of parsed gradient
@param {Object} gradient Parsed gradient
@param {String} prefix Vendor prefix
@returns {String}


function(name, collection) {
			if (!elementTypes[collection])
				elementTypes[collection] = [];

			var col = this.getCollection(collection);
			if (!_.include(col, name))
				col.push(name);
		}

function ( name , collection ) { if ( ! elementTypes [ collection ] ) elementTypes [ collection ] = [ ] ; var col = this . getCollection ( collection ) ; if ( ! _ . include ( col , name ) ) col . push ( name ) ; }

Adds new element into collection
@param {String} name Element name
@param {String} collection Collection name


function(name, collection) {
			if (collection in elementTypes) {
				elementTypes[collection] = _.without(this.getCollection(collection), name);
			}
		}

function ( name , collection ) { if ( collection in elementTypes ) { elementTypes [ collection ] = _ . without ( this . getCollection ( collection ) , name ) ; } }

Removes element name from specified collection
@param {String} name Element name
@param {String} collection Collection name
@returns


function processClassName(name, item) {
		name = transformClassName(name, item, 'element');
		name = transformClassName(name, item, 'modifier');

		// expand class name
		// possible values:
		// * block__element
		// * block__element_modifier
		// * block__element_modifier1_modifier2
		// * block_modifier
		var block = '', element = '', modifier = '';
		var separators = getSeparators();
		if (~name.indexOf(separators.element)) {
			var blockElem = name.split(separators.element);
			var elemModifiers = blockElem[1].split(separators.modifier);

			block = blockElem[0];
			element = elemModifiers.shift();
			modifier = elemModifiers.join(separators.modifier);
		} else if (~name.indexOf(separators.modifier)) {
			var blockModifiers = name.split(separators.modifier);

			block = blockModifiers.shift();
			modifier = blockModifiers.join(separators.modifier);
		}

		if (block || element || modifier) {
			if (!block) {
				block = item.__bem.block;
			}

			// inherit parent bem element, if exists
//			if (item.parent && item.parent.__bem && item.parent.__bem.element)
//				element = item.parent.__bem.element + separators.element + element;

			// produce multiple classes
			var prefix = block;
			var result = [];

			if (element) {
				prefix += separators.element + element;
				result.push(prefix);
			} else {
				result.push(prefix);
			}

			if (modifier) {
				result.push(prefix + separators.modifier + modifier);
			}

			item.__bem.block = block;
			item.__bem.element = element;
			item.__bem.modifier = modifier;

			return result;
		}

		// ...otherwise, return processed or original class name
		return name;
	}

Processes class name
@param {String} name Class name item to process
@param {AbbreviationNode} item Host node for provided class name
@returns Processed class name. May return Array of
class names


function getIndentation(node) {
		if (_.include(prefs.getArray('format.noIndentTags') || [], node.name())) {
			return '';
		}

		return require('resources').getVariable('indentation');
	}

function getIndentation ( node ) { if ( _ . include ( prefs . getArray ( 'format.noIndentTags' ) || [ ] , node . name ( ) ) ) { return '' ; } return require ( 'resources' ) . getVariable ( 'indentation' ) ; }

Get indentation for given node
@param {AbbreviationNode} node
@returns {String}


function shouldBreakInsideInline(node, profile) {
		var abbrUtils = require('abbreviationUtils');
		var hasBlockElems = _.any(node.children, function(child) {
			if (abbrUtils.isSnippet(child))
				return false;

			return !abbrUtils.isInline(child);
		});

		if (!hasBlockElems) {
			return shouldFormatInline(node, profile);
		}

		return true;
	}

function shouldBreakInsideInline ( node , profile ) { var abbrUtils = require ( 'abbreviationUtils' ) ; var hasBlockElems = _ . any ( node . children , function ( child ) { if ( abbrUtils . isSnippet ( child ) ) return false ; return ! abbrUtils . isInline ( child ) ; } ) ; if ( ! hasBlockElems ) { return shouldFormatInline ( node , profile ) ; } return true ; }

Check if we should add line breaks inside inline element
@param {AbbreviationNode} node
@param {OutputProfile} profile
@return {Boolean}


function makeAttributesString(tag, profile) {
		var attrs = '';
		var otherAttrs = [];
		var attrQuote = profile.attributeQuote();
		var cursor = profile.cursor();

		_.each(tag.attributeList(), function(a) {
			var attrName = profile.attributeName(a.name);
			switch (attrName.toLowerCase()) {
				// use short notation for ID and CLASS attributes
				case 'id':
					attrs += '#' + (a.value || cursor);
					break;
				case 'class':
					attrs += '.' + transformClassName(a.value || cursor);
					break;
				// process other attributes
				default:
					otherAttrs.push(':' +attrName + ' => ' + attrQuote + (a.value || cursor) + attrQuote);
			}
		});

		if (otherAttrs.length)
			attrs += '{' + otherAttrs.join(', ') + '}';

		return attrs;
	}

Creates HAML attributes string from tag according to profile settings
@param {AbbreviationNode} tag
@param {Object} profile


function makeAttributesString(node, profile) {
		var attrQuote = profile.attributeQuote();
		var cursor = profile.cursor();

		return _.map(node.attributeList(), function(a) {
			var attrName = profile.attributeName(a.name);
			return ' ' + attrName + '=' + attrQuote + (a.value || cursor) + attrQuote;
		}).join('');
	}

function
makeAttributesString
(
node
,
profile
)
{
var
attrQuote
=
profile
.
attributeQuote
(
)
;
var
cursor
=
profile
.
cursor
(
)
;
return
_
.
map
(
node
.
attributeList
(
)
,
function
(
a
)
{
var
attrName
=
profile
.
attributeName
(
a
.
name
)
;
return
' '
+
attrName
+
'='
+
attrQuote
+
(
a
.
value
||
cursor
)
+
attrQuote
;
}
)
.
join
(
''
)
;
}

Creates HTML attributes string from tag according to profile settings
@param {AbbreviationNode} node
@param {OutputProfile} profile


function(lang, data) {
			if (_.isString(data)) {
				data = {words: _.compact(data.split(' '))};
			} else if (_.isArray(data)) {
				data = {words: data};
			}

			langs[lang] = data;
		}

function
(
lang
,
data
)
{
if
(
_
.
isString
(
data
)
)
{
data
=
{
words
:
_
.
compact
(
data
.
split
(
' '
)
)
}
;
}
else
if
(
_
.
isArray
(
data
)
)
{
data
=
{
words
:
data
}
;
}
langs
[
lang
]
=
data
;
}

Adds new language words for Lorem Ipsum generator
@param {String} lang Two-letter lang definition
@param {Object} data Words for language. Maight be either a space-separated
list of words (String), Array of words or object with text and
common properties


function (err, res) {
    if (err) return sender.emit('transmissionError', err, data.channelURI);
    sender.emit('transmitted', res, data.channelURI);
  }

function ( err , res ) { if ( err ) return sender . emit ( 'transmissionError' , err , data . channelURI ) ; sender . emit ( 'transmitted' , res , data . channelURI ) ; }

called once done of is there was a transmission error


function sanitize(sender, data) {
  data = _.defaults({}, {
    client_id: sender.client_id, client_secret: sender.client_secret
  }, data);

  return _.reduce(data, function (acc, value, key) {
    if (! _.isUndefined(value)) {
      acc[key] = value;
    }
    return acc;
  }, {});
}

function sanitize ( sender , data ) { data = _ . defaults ( { } , { client_id : sender . client_id , client_secret : sender . client_secret } , data ) ; return _ . reduce ( data , function ( acc , value , key ) { if ( ! _ . isUndefined ( value ) ) { acc [ key ] = value ; } return acc ; } , { } ) ; }

merge send data with authorization data,
remove all undefined values

merge send data with authorization data remove all undefined values


function getMissingProps(data) {
  var containsKey = _.partial(_.contains, _.keys(data));
  return _.filter(required, function (req) {
    return !containsKey(req);
  });
}

function getMissingProps ( data ) { var containsKey = _ . partial ( _ . contains , _ . keys ( data ) ) ; return _ . filter ( required , function ( req ) { return ! containsKey ( req ) ; } ) ; }

return an array containing all the missing required
properties


function sendRequiredErrors(sender, missingProps) {
  _.forEach(missingProps, function (prop) {
    sender.emit('error', prop + ' is missing');
  });
}

function
sendRequiredErrors
(
sender
,
missingProps
)
{
_
.
forEach
(
missingProps
,
function
(
prop
)
{
sender
.
emit
(
'error'
,
prop
+
' is missing'
)
;
}
)
;
}

send an error for each missing properties


function Sender(options) {
  EventEmitter.call(this);
  _.extend(this, _.defaults(options || {}, {
    retries: 4
  }));
}

function Sender ( options ) { EventEmitter . call ( this ) ; _ . extend ( this , _ . defaults ( options || { } , { retries : 4 } ) ) ; }

Create a new GCM sender.

@param {object} options
@param {string} options.apiKey
@param {number} options.retries


function appendChild(parent, childrens) {
  if (childrens.length === undefined) childrens = [childrens];
  var docFrag = document.createDocumentFragment(); //we used for loop instead of forEach because childrens can be array like object

  for (var i = 0, ln = childrens.length; i < ln; i++) {
    docFrag.appendChild(childrens[i]);
  }

  parent.appendChild(docFrag);
}

function to append childrens on a parent


function getLogicalPartitions(disk, index, offset, extendedPartitionOffset, limit) {
    return __awaiter(this, void 0, void 0, function* () {
        if (extendedPartitionOffset === undefined) {
            extendedPartitionOffset = offset;
        }
        if (limit === undefined) {
            limit = Infinity;
        }
        const result = [];
        if (limit <= 0) {
            return result;
        }
        const buf = yield readFromDisk(disk, offset, MBR_SIZE);
        for (const p of getPartitionsFromMBRBuf(buf)) {
            if (!p.extended) {
                result.push(mbrPartitionDict(p, offset, index));
            }
            else if (limit > 0) {
                const logicalPartitions = yield getLogicalPartitions(disk, index + 1, extendedPartitionOffset + p.byteOffset(), extendedPartitionOffset, limit - 1);
                result.push(...logicalPartitions);
                return result;
            }
        }
        return result;
    });
}

function getLogicalPartitions ( disk , index , offset , extendedPartitionOffset , limit ) { return __awaiter ( this , void 0 , void 0 , function * ( ) { if ( extendedPartitionOffset === undefined ) { extendedPartitionOffset = offset ; } if ( limit === undefined ) { limit = Infinity ; } const result = [ ] ; if ( limit <= 0 ) { return result ; } const buf = yield readFromDisk ( disk , offset , MBR_SIZE ) ; for ( const p of getPartitionsFromMBRBuf ( buf ) ) { if ( ! p . extended ) { result . push ( mbrPartitionDict ( p , offset , index ) ) ; } else if ( limit > 0 ) { const logicalPartitions = yield getLogicalPartitions ( disk , index + 1 , extendedPartitionOffset + p . byteOffset ( ) , extendedPartitionOffset , limit - 1 ) ; result . push ( ... logicalPartitions ) ; return result ; } } return result ; } ) ; }


function processTemplate(template, variables) {
  for (let variableName of Object.keys(variables)) {
    let value = variables[variableName];
    template = replaceVariable(template, variableName, value);
  }

  template = evaluateConditions(template, variables);

  return template;
}

function processTemplate ( template , variables ) { for ( let variableName of Object . keys ( variables ) ) { let value = variables [ variableName ] ; template = replaceVariable ( template , variableName , value ) ; } template = evaluateConditions ( template , variables ) ; return template ; }

Processes the provided template, replacing variable placeholders with the
values of the variables, and evaluating all conditions.

@param {string} template The template.
@param {Object<string, *>} variables The variables and their values.
@return {string} The processed template.

Processes the provided template replacing variable placeholders with the values of the variables and evaluating all conditions .


function replaceVariable(template, name, value) {
  let key = `{${name}}`;
  let reg = new RegExp(helper.escapeRegExp(key), 'g');
  let encodedReg = new RegExp(helper.escapeRegExp(`{${name}|json}`), 'g');
  let encodedValue = JSON.stringify(value).replace(/<\/script/gi, '<\\/script');

  return template.replace(reg, value).replace(encodedReg, encodedValue);
}

function replaceVariable ( template , name , value ) { let key = ` ${ name } ` ; let reg = new RegExp ( helper . escapeRegExp ( key ) , 'g' ) ; let encodedReg = new RegExp ( helper . escapeRegExp ( ` ${ name } ` ) , 'g' ) ; let encodedValue = JSON . stringify ( value ) . replace ( / <\/script / gi , '<\\/script' ) ; return template . replace ( reg , value ) . replace ( encodedReg , encodedValue ) ; }

Replaces all occurrences of the specified variable in the provided template
with its value.

@param {string} template The template.
@param {string} name The variable name.
@param {?(boolean|number|string|Object<string, ?(boolean|number|string)>|(boolean|number|string|Object<string, ?(boolean|number|string)>)[])} value
The variable value.
@return {string} Processed template.

Replaces all occurrences of the specified variable in the provided template with its value .


function evaluateConditions(template, variables) {
  let source;
  do {
    source = template;
    template = processSimpleConditions(template, variables);
    template = processIfElseConditions(template, variables);
  } while (source !== template);

  return template;
}

function evaluateConditions ( template , variables ) { let source ; do { source = template ; template = processSimpleConditions ( template , variables ) ; template = processIfElseConditions ( template , variables ) ; } while ( source !== template ) ; return template ; }

Processes the if and if-else conditions in the template.

@param {string} template The template to process.
@param {Object<string, *>} variables The variables that should be available
to the predicate expressions in the conditions.
@return {string} Processed template.

Processes the if and if - else conditions in the template .


function processCondition(template, variables, matcher) {
  // eslint-disable-next-line no-constant-condition
  while (true) {
    let matchedCondition = template.match(matcher);
    if (!matchedCondition) {
      break;
    }

    let fragment = matchedCondition[0];
    let conditionCode = matchedCondition[1];

    let testResult = evaluateCondition(conditionCode, variables);
    let replaceWith = testResult
      ? matchedCondition[2]
      : matchedCondition[3] || '';

    template = template.replace(fragment, replaceWith);
  }

  return template;
}

Processes the conditions within the provided template using the provided
condition matcher regexp.

@param {string} template The template to process.
@param {Object<string, *>} variables The variables that should be available
to the predicate expressions in the conditions.
@param {RegExp} matcher The regexp to use to match the conditions in the
template.
@return {string} Processed template.

Processes the conditions within the provided template using the provided condition matcher regexp .


function evaluateCondition(conditionCode, variables) {
  let trimmedCondition = conditionCode.trim();

  if ($Debug) {
    if (!/^!?('[^']*'|"[^"]*")$/.test(trimmedCondition)) {
      throw new Error('Invalid expected value: ' + trimmedCondition);
    }
  }

  let negate = trimmedCondition.charAt(0) === '!';

  let expectedValue = trimmedCondition.slice(negate ? 2 : 1, -1);

  return negate
    ? expectedValue !== variables.$Env
    : expectedValue === variables.$Env;
}

function
evaluateCondition
(
conditionCode
,
variables
)
{
let
trimmedCondition
=
conditionCode
.
trim
(
)
;
if
(
$Debug
)
{
if
(
!
/
^!?('[^']*'|"[^"]*")$
/
.
test
(
trimmedCondition
)
)
{
throw
new
Error
(
'Invalid expected value: '
+
trimmedCondition
)
;
}
}
let
negate
=
trimmedCondition
.
charAt
(
0
)
===
'!'
;
let
expectedValue
=
trimmedCondition
.
slice
(
negate
2
:
1
,
1
)
;
return
negate
expectedValue
!==
variables
.
$Env
:
expectedValue
===
variables
.
$Env
;
}

Evaluates the provided JavaScript condition expression. The function uses
eval, so frequent calls to it may impede performance.

@param {string} conditionCode A JavaScript expression to evaluate.
@param {Object<string, *>} variables The variables that should be available
to the expression to evaluate.
@return {boolean} The result of the evaluated expression, retyped to a
boolean.

Evaluates the provided JavaScript condition expression . The function uses eval so frequent calls to it may impede performance .


function defaultKeyGenerator(request) {
  let protocol = request.protocol;
  let host = request.get('Host');
  let url = request.originalUrl;

  return protocol + ':' + host + url;
}

function defaultKeyGenerator ( request ) { let protocol = request . protocol ; let host = request . get ( 'Host' ) ; let url = request . originalUrl ; return protocol + ':' + host + url ; }

Generates a cache key identifying the provided HTTP request and simillar
requests that may share the same page content.

@param {Express.Request} request The current express.js HTTP request.
@return {string} The generated cache key.

Generates a cache key identifying the provided HTTP request and simillar requests that may share the same page content .


function getOrdersStatistics (metricKeys = ['item_sold_minecraft', 'prepaid_card_redeemed_minecraft']) {
  return fetch(`${CORE_API}/orders/statistics`, {
    method: 'POST',
    body: JSON.stringify({metricKeys}),
    headers: {
      'user-agent': USER_AGENT,
      'content-type': 'application/json',
      'accept': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => res.json())
}

function getOrdersStatistics ( metricKeys = [ 'item_sold_minecraft' , 'prepaid_card_redeemed_minecraft' ] ) { return fetch ( ` ${ CORE_API } ` , { method : 'POST' , body : JSON . stringify ( { metricKeys } ) , headers : { 'user-agent' : USER_AGENT , 'content-type' : 'application/json' , 'accept' : 'application/json' } } ) . then ( handleErrors ) . then ( res => res . json ( ) ) }

Get statistics on the sales of Minecraft.

@param {Array<string>} [metricKeys] - list of metric keys to combine the stats of
@returns {Promise<{total: number, last24: number, saleVelocityPerSeconds: number}[]>} resolves if metricKeys is provided
@see {@link http://wiki.vg/Mojang_API#Statistics}

Get statistics on the sales of Minecraft .


function signout ({username, password}) {
  return fetch(`${YGGDRASIL_API}/signout`, {
    method: 'POST',
    body: JSON.stringify({
      username,
      password
    }),
    headers: {
      'user-agent': USER_AGENT,
      'content-type': 'application/json',
      'accept': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => null)
}

function signout ( { username , password } ) { return fetch ( ` ${ YGGDRASIL_API } ` , { method : 'POST' , body : JSON . stringify ( { username , password } ) , headers : { 'user-agent' : USER_AGENT , 'content-type' : 'application/json' , 'accept' : 'application/json' } } ) . then ( handleErrors ) . then ( res => null ) }

Invalidates access tokens of a given Mojang account.

@param {Object} credentials - account credentials
@param {string} credentials.username - email or username of a Mojang account
@param {string} credentials.password - password for the given account
@returns {Promise} resolves only if credentials were correct
@see {@link http://wiki.vg/Authentication#Signout}
@example
mojang.signout({username, password})
.then(() => console.log('user signed out'))
.catch(err => console.error(err))

Invalidates access tokens of a given Mojang account .


function lookupProfiles (names, agent = 'minecraft') {
  return fetch(`${CORE_API}/profiles/${agent}`, {
    method: 'POST',
    body: JSON.stringify(names),
    headers: {
      'user-agent': USER_AGENT,
      'content-type': 'application/json',
      'accept': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => res.json())
}

function lookupProfiles ( names , agent = 'minecraft' ) { return fetch ( ` ${ CORE_API } ${ agent } ` , { method : 'POST' , body : JSON . stringify ( names ) , headers : { 'user-agent' : USER_AGENT , 'content-type' : 'application/json' , 'accept' : 'application/json' } } ) . then ( handleErrors ) . then ( res => res . json ( ) ) }

Gets a list of abbreviated game profiles from the list of profile names.

*It seems results are reordered alphabetically.*

**You cannot request more than 100 names per request.**

@param {Array<string>} names - list of current profile names (IGN)
@param {string} [agent] - game agent to search within (minecraft, scrolls)
@returns {Promise<Array>} resolves with `[{id, name, legacy?, demo?}]`
@see {@link http://wiki.vg/Mojang_API#Playernames_-.3E_UUIDs}
@example
async function getPaidMinecraftProfiles (names) {
const list = await getProfiles(names)
return list.filter(({demo}) => !demo)
}

Gets a list of abbreviated game profiles from the list of profile names .


function getSession (profileId) {
  return fetch(`${SESSION_API}/session/minecraft/profile/${profileId}`, {
    headers: {
      'user-agent': USER_AGENT,
      'accept': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => {
      if (res.status === 204) throw new Error('no such profile')
      return res.json()
    })
    .then(({id, name, properties}) => {
      const {timestamp, textures} = JSON.parse(Base64.decode(properties[0].value))
      const {SKIN, CAPE} = textures
      return {
        id,
        name,
        timestamp,
        skin: SKIN && SKIN.url,
        cape: CAPE && CAPE.url,
        isSlim: SKIN && SKIN.metadata && SKIN.metadata.model === 'slim'
      }
    })
}

function
getSession
(
profileId
)
{
return
fetch
(
`
${
SESSION_API
}
${
profileId
}
`
,
{
headers
:
{
'user-agent'
:
USER_AGENT
,
'accept'
:
'application/json'
}
}
)
.
then
(
handleErrors
)
.
then
(
res
=>
{
if
(
res
.
status
===
204
)
throw
new
Error
(
'no such profile'
)
return
res
.
json
(
)
}
)
.
then
(
(
{
id
,
name
,
properties
}
)
=>
{
const
{
timestamp
,
textures
}
=
JSON
.
parse
(
Base64
.
decode
(
properties
[
0
]
.
value
)
)
const
{
SKIN
,
CAPE
}
=
textures
return
{
id
,
name
,
timestamp
,
skin
:
SKIN
&&
SKIN
.
url
,
cape
:
CAPE
&&
CAPE
.
url
,
isSlim
:
SKIN
&&
SKIN
.
metadata
&&
SKIN
.
metadata
.
model
===
'slim'
}
}
)
}

A special service that returns a profile's name, skin URL, and cape URL.

*This endpoint is used by the official launcher to load the current skin.*

**Rate limit: You can request the same profile once per minute.**

@param {string} profileId - profile UUID of a player
@returns {Promise<CustomSession>} resolves if profile exists
@see {@link http://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape}

A special service that returns a profile s name skin URL and cape URL .


function uploadSkin ({accessToken}, profileId, file, isSlim = false) {
  const form = new FormData()
  form.append('model', isSlim ? 'slim' : '')
  form.append('file', file)

  return fetch(`${CORE_API}/user/profile/${profileId}/skin`, {
    method: 'PUT',
    body: form,
    headers: {
      'user-agent': USER_AGENT,
      'authorization': `Bearer ${accessToken}`
    }
  })
    .then(handleErrors)
    .then(res => null)
}

function uploadSkin ( { accessToken } , profileId , file , isSlim = false ) { const form = new FormData ( ) form . append ( 'model' , isSlim ? 'slim' : '' ) form . append ( 'file' , file ) return fetch ( ` ${ CORE_API } ${ profileId } ` , { method : 'PUT' , body : form , headers : { 'user-agent' : USER_AGENT , 'authorization' : ` ${ accessToken } ` } } ) . then ( handleErrors ) . then ( res => null ) }

Uploads a file as a logged-in user's in-game skin.

@param {Object} session - object from authentication
@param {string} session.accessToken - valid access token for the user's account
@param {string} profileId - which profile UUID to update the skin of
@param {Stream|Buffer} file - new skin's binary image data
@param {boolean} [isSlim] - if true, the slim model will be used with the skin
@returns {Promise} resolves if access token, profile UUID, and stream are valid
@see {@link http://wiki.vg/Mojang_API#Change_Skin}

Uploads a file as a logged - in user s in - game skin .


function getUser ({accessToken}) {
  return fetch(`${CORE_API}/user`, {
    headers: {
      'user-agent': USER_AGENT,
      'authorization': `Bearer ${accessToken}`,
      'accept': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => res.json())
}

function getUser ( { accessToken } ) { return fetch ( ` ${ CORE_API } ` , { headers : { 'user-agent' : USER_AGENT , 'authorization' : ` ${ accessToken } ` , 'accept' : 'application/json' } } ) . then ( handleErrors ) . then ( res => res . json ( ) ) }

Gets a logged-in user's private Mojang account information.

@param {Object} session - object from authentication
@param {string} session.accessToken - valid access token for the user's account
@returns {Promise<MojangUser>} resolves if access token is valid
@see {@link http://wiki.vg/Mojang_API#User_Info}

Gets a logged - in user s private Mojang account information .


function invalidate ({accessToken, clientToken}) {
  return fetch(`${YGGDRASIL_API}/invalidate`, {
    method: 'POST',
    body: JSON.stringify({
      accessToken,
      clientToken
    }),
    headers: {
      'user-agent': USER_AGENT,
      'content-type': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => null)
}

function invalidate ( { accessToken , clientToken } ) { return fetch ( ` ${ YGGDRASIL_API } ` , { method : 'POST' , body : JSON . stringify ( { accessToken , clientToken } ) , headers : { 'user-agent' : USER_AGENT , 'content-type' : 'application/json' } } ) . then ( handleErrors ) . then ( res => null ) }

Invalidate an access token.

@param {Object} session - a session to end/invalidate
@param {string} session.accessToken - a session access token
@param {string=} session.clientToken - must match the one used to obtain the access token
@returns {Promise} always resolves, regardless if tokens were valid
@see {@link http://wiki.vg/Authentication#Invalidate}
@example
const session = await mojang.authenticate(credentials)
console.log('logged in', session.selectedProfile.name)
await mojang.invalidate(session)
console.log('logged out')


function getBlockedServers () {
  return fetch(`${SESSION_API}/blockedservers`, {
    headers: {
      'user-agent': USER_AGENT
    }
  })
    .then(handleErrors)
    .then(res => res.text())
    .then(text => text.split('\n').slice(0, -1))
}

function getBlockedServers ( ) { return fetch ( ` ${ SESSION_API } ` , { headers : { 'user-agent' : USER_AGENT } } ) . then ( handleErrors ) . then ( res => res . text ( ) ) . then ( text => text . split ( '\n' ) . slice ( 0 , - 1 ) ) }

Returns a list of SHA1 hashes used to check server addresses against when the client tries to connect.

*See {@link http://wiki.vg/Mojang_API#Blocked_Servers} for how this list is used.*

@returns {Promise<Array<string>>} resolves to a list of address hashes

Returns a list of SHA1 hashes used to check server addresses against when the client tries to connect .


function status () {
  return fetch(`${STATUS_API}/check`, {
    headers: {
      'user-agent': USER_AGENT,
      'accept': 'application/json'
    }
  })
    .then(handleErrors) // NOTE I wonder how /check can fail
    .then(res => res.json())
    .then((sites) => sites.reduce((acc, val) => {
      const hostname = Object.keys(val)[0]
      acc.push({
        hostname,
        color: val[hostname],
        isAvailable: val[hostname] === 'green' || val[hostname] === 'yellow',
        hasIssues: val[hostname] === 'yellow' || val[hostname] === 'red'
      })
      return acc
    }, []))
}

function
status
(
)
{
return
fetch
(
`
${
STATUS_API
}
`
,
{
headers
:
{
'user-agent'
:
USER_AGENT
,
'accept'
:
'application/json'
}
}
)
.
then
(
handleErrors
)
// NOTE I wonder how /check can fail
.
then
(
res
=>
res
.
json
(
)
)
.
then
(
(
sites
)
=>
sites
.
reduce
(
(
acc
,
val
)
=>
{
const
hostname
=
Object
.
keys
(
val
)
[
0
]
acc
.
push
(
{
hostname
,
color
:
val
[
hostname
]
,
isAvailable
:
val
[
hostname
]
===
'green'
||
val
[
hostname
]
===
'yellow'
,
hasIssues
:
val
[
hostname
]
===
'yellow'
||
val
[
hostname
]
===
'red'
}
)
return
acc
}
,
[
]
)
)
}

Returns status of various Mojang services in a more helpful format.

@returns {Promise<Array>} resolves a list like `[{hostname, color, isAvailable, hasIssues}]`
@see {@link http://wiki.vg/Mojang_API#API_Status}

Returns status of various Mojang services in a more helpful format .


function getProfileHistory (profileId) {
  // BASE/user/profile/:id/names also seems to provide the same endpoint
  return fetch(`${CORE_API}/user/profiles/${profileId}/names`, {
    headers: {
      'user-agent': USER_AGENT,
      'accept': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => {
      if (res.status === 204) throw new Error(`no such profile: ${profileId}`)
      return res.json()
    })
}

Gets all name changes for a given player's profile UUID.

@param {string} profileId - profile UUID (does not work with user UUID)
@returns {Promise<{name: string, changedToAt: number}[]>} resolves if profile exists
@see {@link http://wiki.vg/Mojang_API#UUID_-.3E_Name_history}

Gets all name changes for a given player s profile UUID .


function authenticate ({username, password, clientToken, agent}) {
  return fetch(`${YGGDRASIL_API}/authenticate`, {
    method: 'POST',
    body: JSON.stringify({
      agent,
      username,
      password,
      clientToken,
      requestUser: true
    }),
    headers: {
      'user-agent': USER_AGENT,
      'Content-Type': 'application/json',
      'accept': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => res.json())
}

function authenticate ( { username , password , clientToken , agent } ) { return fetch ( ` ${ YGGDRASIL_API } ` , { method : 'POST' , body : JSON . stringify ( { agent , username , password , clientToken , requestUser : true } ) , headers : { 'user-agent' : USER_AGENT , 'Content-Type' : 'application/json' , 'accept' : 'application/json' } } ) . then ( handleErrors ) . then ( res => res . json ( ) ) }

Authenticates a user with their Mojang credentials.

*Use `agent` if session is for a game client, ie. a Minecraft launcher.*

**Handle access tokens securely, but they do invalidate easily.**

@param {Object} credentials - the payload of the auth request
@param {string} credentials.username - email or username of a Mojang account
@param {string} credentials.password - password for the given account
@param {string=} credentials.clientToken - if empty, server will generate a client token
@param {Object=} credentials.agent - if valid, adds `selectedProfile` to response
@param {string} credentials.agent.name - name of the agent ('Minecraft' or 'Scrolls')
@param {number} credentials.agent.version - version number of the agent (use `1`)
@returns {Promise<MojangSession>} resolves if credentials are valid
@see {@link http://wiki.vg/Authentication#Authenticate}
@example
const clientToken = 'loaded from settings'
const agent = {name: 'Minecraft', version: 1}
const session = await mojang.authenticate({username, password, clientToken, agent})
console.debug('access token', session.accessToken)
console.debug('profile id', session.selectedProfile.id)
console.debug('minecraft ign', session.selectedProfile.name)
console.debug('user id', session.user.id)

Authenticates a user with their Mojang credentials .


function isValid ({accessToken, clientToken}) {
  return fetch(`${YGGDRASIL_API}/validate`, {
    method: 'POST',
    body: JSON.stringify({
      accessToken,
      clientToken
    }),
    headers: {
      'user-agent': USER_AGENT,
      'content-type': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => true)
    .catch(err => {
      if (err.message === 'Invalid token') return false
      throw err
    })
}

function
isValid
(
{
accessToken
,
clientToken
}
)
{
return
fetch
(
`
${
YGGDRASIL_API
}
`
,
{
method
:
'POST'
,
body
:
JSON
.
stringify
(
{
accessToken
,
clientToken
}
)
,
headers
:
{
'user-agent'
:
USER_AGENT
,
'content-type'
:
'application/json'
}
}
)
.
then
(
handleErrors
)
.
then
(
res
=>
true
)
.
catch
(
err
=>
{
if
(
err
.
message
===
'Invalid token'
)
return
false
throw
err
}
)
}

Checks if an access token is suitable for use with a Minecraft server.

@param {Object} session - a session access token
@param {string} session.accessToken - a session access token
@param {string=} session.clientToken - must match the one used to obtain the access token
@returns {Promise<boolean>} resolves true/false if token is valid/invalid
@see {@link http://wiki.vg/Authentication#Validate}
@example
if (await mojang.isValid(session)) {
console.debug('access token still good')
} else {
console.debug('access token has gone bad')
}

Checks if an access token is suitable for use with a Minecraft server .


function lookupProfileAt (name, date, agent = 'minecraft') {
  const hasDate = typeof date !== 'undefined'
  const query = hasDate
    ? `/users/profiles/${agent}/${name}?at=${date}`
    : `/users/profiles/${agent}/${name}`

  return fetch(`${CORE_API}${query}`, {
    headers: {
      'user-agent': USER_AGENT,
      'accept': 'application/json'
    }
  })
    .then(handleErrors)
    .then(res => {
      if (res.status === 204) throw new Error(`so such name at time: ${name}`)
      return res.json()
    })
}

function lookupProfileAt ( name , date , agent = 'minecraft' ) { const hasDate = typeof date !== 'undefined' const query = hasDate ? ` ${ agent } ${ name } ${ date } ` : ` ${ agent } ${ name } ` return fetch ( ` ${ CORE_API } ${ query } ` , { headers : { 'user-agent' : USER_AGENT , 'accept' : 'application/json' } } ) . then ( handleErrors ) . then ( res => { if ( res . status === 204 ) throw new Error ( ` ${ name } ` ) return res . json ( ) } ) }

Gets an abbreviated game profile at a given timestamp for an IGN name.

**Prefer {@link lookupProfiles} when looking up current data.**

@param {string} name - current profile name (IGN) of the user
@param {number} [date] - UNIX timestamp to check the username at
@param {string} [agent] - game agent to check against
@returns {Promise<Object>} resolves with `{id, name, legacy?, demo?}`
@see {@link http://wiki.vg/Mojang_API#Username_-.3E_UUID_at_time}
@example
const {id, name, legacy, demo} = await lookupProfileAt('Notch', 1503335853700)

Gets an abbreviated game profile at a given timestamp for an IGN name .


function setSkin ({accessToken}, profileId, skinUrl, isSlim = false) {
  const params = new URLSearchParams()
  params.append('model', isSlim ? 'slim' : '')
  params.append('url', skinUrl)

  return fetch(`${CORE_API}/user/profile/${profileId}/skin`, {
    method: 'POST',
    body: params,
    headers: {
      'user-agent': USER_AGENT,
      'authorization': `Bearer ${accessToken}`
    }
  })
    .then(handleErrors)
    .then(res => null)
}

function setSkin ( { accessToken } , profileId , skinUrl , isSlim = false ) { const params = new URLSearchParams ( ) params . append ( 'model' , isSlim ? 'slim' : '' ) params . append ( 'url' , skinUrl ) return fetch ( ` ${ CORE_API } ${ profileId } ` , { method : 'POST' , body : params , headers : { 'user-agent' : USER_AGENT , 'authorization' : ` ${ accessToken } ` } } ) . then ( handleErrors ) . then ( res => null ) }

Sets a logged-in user's in-game skin to an online image.

@param {Object} session - object from authentication
@param {string} session.accessToken - valid access token for the user's account
@param {string} profileId - which profile UUID to update the skin of
@param {string} skinUrl - Internet-accessible URL of the image file
@param {boolean} [isSlim] - if true, the slim model will be used with the skin
@returns {Promise} resolves if access token, profile UUID, and skin URL are valid
@see {@link http://wiki.vg/Mojang_API#Change_Skin}

Sets a logged - in user s in - game skin to an online image .