Comparing version 3.4.0 to 4.0.0
{ | ||
"name": "tartan", | ||
"version": "3.4.0", | ||
"description": "Library to render tartan pattern using its textual descriptor (threadcount).", | ||
"version": "4.0.0", | ||
"description": "This library allows to parse tartan threadcount.", | ||
"keywords": [ | ||
@@ -30,7 +30,7 @@ "tartan", | ||
"bugs": { | ||
"url": "https://github.com/kravets-levko/tartan/issues" | ||
"url": "https://github.com/thetartan/tartan/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/kravets-levko/tartan.git" | ||
"url": "https://github.com/thetartan/tartan.git" | ||
}, | ||
@@ -37,0 +37,0 @@ "dependencies": { |
@@ -6,2 +6,3 @@ 'use strict'; | ||
module.exports.weave = { | ||
// Thread above warp, threads under warp | ||
plain: [1, 1], | ||
@@ -11,3 +12,3 @@ serge: [2, 2] | ||
module.exports.colors = utils.normalizeColorMap({ | ||
module.exports.colors = utils.color.buildColorMap({ | ||
/* eslint-disable key-spacing */ | ||
@@ -20,5 +21,7 @@ B: '#304080', G: '#004c00', K: '#000000', | ||
module.exports.warpAndWeftSeparator = '//'; | ||
module.exports.insignificantTokens = [ | ||
utils.TokenType.invalid, | ||
utils.TokenType.whitespace | ||
'invalid', | ||
'whitespace' | ||
]; |
@@ -20,5 +20,3 @@ 'use strict'; | ||
module.exports.matchSquareBrackets = require('./match-square-brackets'); | ||
module.exports.pivotsToSquareBrackets = require('./pivots-to-square-brackets'); | ||
module.exports.classify = require('./classify'); | ||
module.exports.removeTokens = require('./remove-tokens'); | ||
module.exports.removeZeroWidthStripes = require('./remove-zero-width-stripes'); |
@@ -7,11 +7,9 @@ 'use strict'; | ||
module.exports.defaults = require('./defaults'); | ||
module.exports.parse = require('./parse'); | ||
module.exports.filter = require('./filter'); | ||
module.exports.transform = require('./transform'); | ||
module.exports.syntax = require('./syntax'); | ||
module.exports.transform = require('./transform'); | ||
module.exports.render = require('./render'); | ||
module.exports.schema = require('./schema'); | ||
module.exports.utils = require('./utils'); | ||
module.exports.helpers = require('./helpers'); | ||
module.exports.defaults = require('./defaults'); |
'use strict'; | ||
var _ = require('lodash'); | ||
var lexer = require('./tokenize'); | ||
var tokenize = require('./tokenize'); | ||
var defaultOptions = { | ||
// function to filter parsed tokens: (tokens) => { return modifiedTokens; } | ||
filterTokens: null, | ||
// Function to build AST: (tokens) => { return newAST; } | ||
buildSyntaxTree: null | ||
errorHandler: null, | ||
processTokens: null, | ||
buildSyntaxTree: null, | ||
foreseeLimit: 1 | ||
}; | ||
function factory(parsers, options) { | ||
var tokenize = lexer(parsers, options); | ||
options = _.extend({}, defaultOptions, options); | ||
return function(source) { | ||
if (!_.isString(source)) { | ||
return null; | ||
var context = tokenize(source, parsers, options); | ||
var result = context.parse(); | ||
if (_.isFunction(options.processTokens)) { | ||
result = options.processTokens(result); | ||
} | ||
var result = tokenize(source); | ||
if (_.isFunction(options.filterTokens)) { | ||
result = options.filterTokens(result); | ||
} | ||
if (_.isFunction(options.buildSyntaxTree)) { | ||
@@ -27,0 +23,0 @@ result = options.buildSyntaxTree(result); |
@@ -9,37 +9,19 @@ 'use strict'; | ||
allowLongNames: true, | ||
// Use '=' symbol between name and value: 'none', 'allow', 'require' | ||
valueAssignment: 'allow', | ||
// Use '#' as color prefix: 'none', 'allow', 'require' | ||
colorPrefix: 'require', | ||
// Regular expression or string; case-insensitive | ||
colorPrefix: /[=]?[#]/, | ||
// Regular expression or string; case-insensitive | ||
colorSuffix: /;?/, | ||
// Formats: `short` (#fc0), `long` (#ffcc00) or `both` | ||
colorFormat: 'both', | ||
// Comment after color value: 'none', 'allow', 'require'. | ||
// If `comment` != 'none' and `whitespaceBeforeComment` != 'require', | ||
// `colorFormat` is forced to `long` | ||
comment: 'none', | ||
// Whitespace between value and comment: 'none', 'allow', 'require'. | ||
// Ignored if `comment` options has value 'none' | ||
whitespaceBeforeComment: 'require', | ||
// Semicolon at the end of color definition: 'none', 'allow', 'require' | ||
semicolonAtTheEnd: 'allow' | ||
allowComment: false, | ||
// Regular expression or string; case-insensitive | ||
commentSuffix: /;/, | ||
requireCommentSuffix: true, | ||
// Regular expression; value of first group will be used to modify | ||
// comment (if available) | ||
commentFormat: /^\s*(.*)\s*;\s*$/ | ||
}; | ||
function validateOptions(options) { | ||
var keys = [ | ||
'valueAssignment', | ||
'colorPrefix', | ||
'comment', | ||
'whitespaceBeforeComment', | ||
'semicolonAtTheEnd' | ||
]; | ||
var values = ['none', 'allow', 'require']; | ||
_.each(keys, function(key) { | ||
var value = utils.trim(('' + options[key]).toLowerCase()); | ||
if (values.indexOf(value) == -1) { | ||
value = defaultOptions[key]; | ||
} | ||
options[key] = value; | ||
}); | ||
options.colorFormat = utils.trim(('' + options.colorFormat).toLowerCase()); | ||
options.colorFormat = _.trim(('' + options.colorFormat).toLowerCase()); | ||
if (['long', 'short', 'both'].indexOf(options.colorFormat) == -1) { | ||
@@ -49,16 +31,28 @@ options.colorFormat = defaultOptions.colorFormat; | ||
// If comment is allowed and may be not separated from color | ||
// by a whitespace, require long color format - since it is the only | ||
// 100% way to extract color value | ||
if (options.comment != 'none') { | ||
if (options.whitespaceBeforeComment != 'require') { | ||
options.colorFormat = 'long'; | ||
} | ||
if (options.colorPrefix instanceof RegExp) { | ||
options.colorPrefix = options.colorPrefix.source; | ||
} else | ||
if (!_.isString(options.colorPrefix)) { | ||
options.colorPrefix = ''; | ||
} | ||
// If color prefix is 'none', require value assignment | ||
if (options.colorPrefix == 'none') { | ||
options.valueAssignment = 'require'; | ||
if (options.colorSuffix instanceof RegExp) { | ||
options.colorSuffix = options.colorSuffix.source; | ||
} else | ||
if (!_.isString(options.colorSuffix)) { | ||
options.colorSuffix = ''; | ||
} | ||
if (options.commentSuffix instanceof RegExp) { | ||
var flags = options.commentSuffix.ignoreCase ? 'i' : ''; | ||
options.commentSuffix = new RegExp( | ||
'^' + options.commentSuffix.source, flags); | ||
} else { | ||
options.commentSuffix = null; | ||
} | ||
if (!(options.commentFormat instanceof RegExp)) { | ||
options.commentFormat = null; | ||
} | ||
return options; | ||
@@ -68,3 +62,2 @@ } | ||
function buildRegExp(options) { | ||
/* eslint-disable max-statements-per-line */ | ||
var result = ['^']; | ||
@@ -79,16 +72,4 @@ | ||
// Value assignment | ||
switch (options.valueAssignment) { | ||
case 'allow': result.push('=?'); break; | ||
case 'require': result.push('='); break; | ||
default: break; | ||
} | ||
// Color format | ||
switch (options.colorPrefix) { | ||
case 'allow': result.push('#?'); break; | ||
case 'require': result.push('#'); break; | ||
default: break; | ||
} | ||
result.push('(' + options.colorPrefix + ')'); | ||
switch (options.colorFormat) { | ||
@@ -107,53 +88,79 @@ case 'long': | ||
} | ||
result.push(options.colorSuffix); | ||
// Comments | ||
if (options.comment != 'none') { | ||
switch (options.whitespaceBeforeComment) { | ||
case 'allow': result.push('\\s?'); break; | ||
case 'require': result.push('\\s'); break; | ||
default: break; | ||
} | ||
part = ''; | ||
switch (options.semicolonAtTheEnd) { | ||
case 'none': part = '[^\\s]'; break; | ||
case 'allow': part = '[^;\\s]'; break; | ||
case 'require': part = '[^;]'; break; | ||
default: break; | ||
} | ||
switch (options.comment) { | ||
case 'allow': result.push('(' + part + '*)'); break; | ||
case 'require': result.push('(' + part + '+)'); break; | ||
default: break; | ||
} | ||
} | ||
// Semicolon at the end | ||
switch (options.semicolonAtTheEnd) { | ||
case 'none': break; | ||
case 'allow': result.push(';?'); break; | ||
case 'require': result.push(';'); break; | ||
default: break; | ||
} | ||
return new RegExp(result.join(''), 'i'); | ||
/* eslint-enable max-statements-per-line */ | ||
} | ||
function parser(str, offset, pattern) { | ||
function parser(context, offset, pattern, options) { | ||
var source = context.source; | ||
var matches; | ||
var chunk; | ||
var i; | ||
// Color definition can have at most 107 characters | ||
str = str.substr(offset, 110); | ||
chunk = source.substr(offset, 200); | ||
matches = pattern.exec(str); | ||
matches = pattern.exec(chunk); | ||
if (matches) { | ||
return { | ||
type: utils.TokenType.color, | ||
var result = { | ||
type: utils.token.color, | ||
name: matches[1].toUpperCase(), | ||
color: utils.normalizeColor('#' + matches[2]), | ||
comment: utils.trim(matches[3]), | ||
// matches[2] is color prefix | ||
color: utils.color.normalizeColor(matches[3]), | ||
comment: '', | ||
length: matches[0].length | ||
}; | ||
if (!context.inForesee) { | ||
if (options.allowComment) { | ||
var commentOffset = offset + result.length; | ||
if (options.commentSuffix && options.requireCommentSuffix) { | ||
// Fast case - just search for suffix | ||
for (i = commentOffset; i < source.length; i++) { | ||
chunk = source.substr(i, 10); | ||
matches = options.commentSuffix.exec(chunk); | ||
if (matches) { | ||
result.comment = source.substr(commentOffset, | ||
i - commentOffset + matches[0].length); | ||
break; | ||
} | ||
} | ||
if (i >= source.length) { | ||
result.comment = source.substr(commentOffset, source.length); | ||
} | ||
} else { | ||
var ignoreTokens = ['whitespace', 'invalid']; | ||
// Slow - search for next token or suffix (if available) | ||
for (i = commentOffset; i < source.length; i++) { | ||
chunk = source.substr(i, 10); | ||
matches = options.commentSuffix.exec(chunk); | ||
if (matches) { | ||
result.comment = source.substr(commentOffset, | ||
i - commentOffset + matches[0].length); | ||
break; | ||
} | ||
var token = context.foresee(i); | ||
if (_.isObject(token) && (ignoreTokens.indexOf(token.type) == -1)) { | ||
result.comment = source.substr(commentOffset, | ||
i - commentOffset); | ||
break; | ||
} | ||
} | ||
if (i >= source.length) { | ||
result.comment = source.substr(commentOffset, source.length); | ||
} | ||
} | ||
} | ||
result.length += result.comment.length; | ||
if (options.commentFormat) { | ||
matches = options.commentFormat.exec(result.comment); | ||
if (matches && _.isString(matches[1])) { | ||
result.comment = matches[1]; | ||
} | ||
} | ||
result.comment = _.trim(result.comment); | ||
} | ||
return result; | ||
} | ||
@@ -165,4 +172,4 @@ } | ||
var pattern = buildRegExp(options); | ||
return function(str, offset) { | ||
return parser(str, offset, pattern); | ||
return function(context, offset) { | ||
return parser(context, offset, pattern, options); | ||
}; | ||
@@ -169,0 +176,0 @@ } |
@@ -5,22 +5,41 @@ 'use strict'; | ||
var utils = require('../../utils'); | ||
var errors = require('../../errors'); | ||
var defaultOptions = { | ||
allowInvalidTokens: false | ||
}; | ||
function parse(context, offset) { | ||
var source = context.source; | ||
var result = source.charAt(offset); | ||
function factory(options) { | ||
options = _.extend({}, defaultOptions, options); | ||
return function(str, offset) { | ||
if (!options.allowInvalidTokens) { | ||
throw new errors.InvalidToken(str, offset); | ||
if (!context.inForesee) { | ||
var foreseeOffset = offset + 1; | ||
while (true) { | ||
var token = context.foresee(foreseeOffset); | ||
if (_.isObject(token) && (token.type == 'invalid')) { | ||
result += token.value; | ||
foreseeOffset += token.length; | ||
continue; | ||
} | ||
break; | ||
} | ||
return { | ||
type: utils.TokenType.invalid, | ||
value: str.charAt(offset), | ||
length: 1 | ||
} | ||
if (result != '') { | ||
result = { | ||
type: utils.token.invalid, | ||
value: result, | ||
length: result.length | ||
}; | ||
}; | ||
if (!context.inForesee) { | ||
context.errorHandler( | ||
new Error(utils.error.message.invalidToken), | ||
{token: result}, | ||
utils.error.severity.error | ||
); | ||
} | ||
return result; | ||
} | ||
} | ||
function factory() { | ||
return parse; | ||
} | ||
module.exports = factory; |
@@ -11,9 +11,10 @@ 'use strict'; | ||
function parser(str, offset, options) { | ||
function parser(context, offset, options) { | ||
var source = context.source; | ||
if (options.string != '') { | ||
var s = str.substr(offset, options.string.length); | ||
var s = source.substr(offset, options.string.length); | ||
var q = options.ignoreCase ? s.toUpperCase() : s; | ||
if (q == options.string) { | ||
return { | ||
type: utils.TokenType.literal, | ||
type: utils.token.literal, | ||
value: s, | ||
@@ -20,0 +21,0 @@ length: s.length |
@@ -5,6 +5,4 @@ 'use strict'; | ||
var utils = require('../../utils'); | ||
var errors = require('../../errors'); | ||
var defaultOptions = { | ||
allowZeroWidthStripes: false, | ||
// Name can have more than one character | ||
@@ -30,15 +28,14 @@ allowLongNames: true | ||
function parser(str, offset, pattern, options) { | ||
// Hope nobody will try to add stripe with 1e9 lines... | ||
var matches = pattern.exec(str.substr(offset, 10)); | ||
function parser(context, offset, pattern, options) { | ||
var source = context.source; | ||
// Hope nobody will try to add stripe with 1e19 lines... | ||
var matches = pattern.exec(source.substr(offset, 20)); | ||
if (matches) { | ||
var count = parseInt(matches[2], 10) || 0; | ||
if (count <= 0) { | ||
if (!options.allowZeroWidthStripes) { | ||
throw new errors.ZeroWidthStripe(str, offset, matches[0].length); | ||
} | ||
if (count < 0) { | ||
count = 0; | ||
} | ||
return { | ||
type: utils.TokenType.pivot, | ||
var result = { | ||
type: utils.token.pivot, | ||
name: matches[1].toUpperCase(), | ||
@@ -48,2 +45,12 @@ count: count, | ||
}; | ||
if (result.count == 0) { | ||
context.errorHandler( | ||
new Error(utils.error.message.zeroWidthStripe), | ||
{token: result}, | ||
utils.error.severity.warning | ||
); | ||
} | ||
return result; | ||
} | ||
@@ -50,0 +57,0 @@ } |
@@ -5,6 +5,4 @@ 'use strict'; | ||
var utils = require('../../utils'); | ||
var errors = require('../../errors'); | ||
var defaultOptions = { | ||
allowZeroWidthStripes: false, | ||
// Name can have more than one character | ||
@@ -30,15 +28,14 @@ allowLongNames: true | ||
function parser(str, offset, pattern, options) { | ||
// Hope nobody will try to add stripe with 1e9 lines... | ||
var matches = pattern.exec(str.substr(offset, 10)); | ||
function parser(context, offset, pattern, options) { | ||
var source = context.source; | ||
// Hope nobody will try to add stripe with 1e19 lines... | ||
var matches = pattern.exec(source.substr(offset, 20)); | ||
if (matches) { | ||
var count = parseInt(matches[2], 10) || 0; | ||
if (count <= 0) { | ||
if (!options.allowZeroWidthStripes) { | ||
throw new errors.ZeroWidthStripe(str, offset, matches[0].length); | ||
} | ||
if (count < 0) { | ||
count = 0; | ||
} | ||
return { | ||
type: utils.TokenType.stripe, | ||
var result = { | ||
type: utils.token.stripe, | ||
name: matches[1].toUpperCase(), | ||
@@ -48,2 +45,12 @@ count: count, | ||
}; | ||
if (result.count == 0) { | ||
context.errorHandler( | ||
new Error(utils.error.message.zeroWidthStripe), | ||
{token: result}, | ||
utils.error.severity.warning | ||
); | ||
} | ||
return result; | ||
} | ||
@@ -50,0 +57,0 @@ } |
'use strict'; | ||
var pattern = /^\s+/i; | ||
var utils = require('../../utils'); | ||
var pattern = /^\s+/i; | ||
function parse(context, offset) { | ||
var source = context.source; | ||
var chunkSize = 10; | ||
var result = ''; | ||
while (true) { | ||
var chunk = source.substr(offset, chunkSize); | ||
var matches = pattern.exec(chunk); | ||
if (!matches) { | ||
break; | ||
} | ||
result += matches[0]; | ||
if (matches[0].length < chunkSize) { | ||
// Don't wait for next turn | ||
break; | ||
} | ||
} | ||
function parser(str, offset) { | ||
// Try to capture at most 10 characters. If there are more | ||
// whitespaces - we'll capture them on a next turn | ||
var matches = pattern.exec(str.substr(offset, 10)); | ||
if (matches) { | ||
if (result != '') { | ||
return { | ||
type: utils.TokenType.whitespace, | ||
value: matches[0], | ||
length: matches[0].length | ||
type: utils.token.whitespace, | ||
value: result, | ||
length: result.length | ||
}; | ||
@@ -21,5 +33,5 @@ } | ||
function factory() { | ||
return parser; | ||
return parse; | ||
} | ||
module.exports = factory; |
'use strict'; | ||
var _ = require('lodash'); | ||
var utils = require('../utils'); | ||
var whitespace = require('./token/whitespace'); | ||
var invalid = require('./token/invalid'); | ||
function canBeMerged(token) { | ||
return utils.isInvalid(token) || utils.isWhitespace(token); | ||
} | ||
function Context(source, parsers, options) { | ||
this.source = _.isString(source) ? source : ''; | ||
function appendToken(tokens, token) { | ||
if (canBeMerged(token)) { | ||
var last = _.last(tokens); | ||
if (last && canBeMerged(last)) { | ||
if (last.type == token.type) { | ||
last.value += token.value; | ||
last.length += token.length; | ||
return; | ||
} | ||
} | ||
parsers = _.filter(parsers, _.isFunction); | ||
parsers.splice(0, 0, whitespace()); // Prepend this parser to skip spaces | ||
parsers.push(invalid()); // This parser will handle invalid tokens | ||
this.parsers = parsers; | ||
this.options = options; | ||
this.inForesee = 0; | ||
if (_.isFunction(options.errorHandler)) { | ||
this.errorHandler = function(error, data, severity) { | ||
options.errorHandler(error, data, severity || 'error'); | ||
}; | ||
} | ||
tokens.push(token); | ||
} | ||
function executeParsers(source, parsers, offset, result) { | ||
_.each(parsers, function(parser) { | ||
var token = parser(source, offset); | ||
if (_.isObject(token)) { | ||
token.offset = token.offset || offset; | ||
token.source = source; | ||
appendToken(result, token); | ||
offset = token.offset + token.length; | ||
return false; | ||
Context.prototype = {}; | ||
Context.prototype.errorHandler = function(error, data, severity) { | ||
// Do nothing - default error handler will just ignore all errors. | ||
}; | ||
function getToken(context, offset) { | ||
var result = null; | ||
_.each(context.parsers, function(parser) { | ||
result = parser(context, offset); | ||
if (_.isObject(result)) { | ||
result.offset = result.offset || offset; | ||
return false; // Break | ||
} | ||
}); | ||
return offset; | ||
return result; | ||
} | ||
function factory(parsers, options) { | ||
parsers = _.filter(parsers, _.isFunction); | ||
parsers.splice(0, 0, whitespace()); // Prepend this parser to skip spaces | ||
parsers.push(invalid(options)); // This parser will handle invalid tokens | ||
Context.prototype.foresee = function(offset) { | ||
var foreseeLimit = this.options.foreseeLimit; | ||
if (this.inForesee >= foreseeLimit) { | ||
return null; | ||
} | ||
return function(source) { | ||
var tokens = []; | ||
var offset = 0; | ||
offset = parseInt(offset, 10) || 0; | ||
if (offset < 0) { | ||
offset = 0; | ||
} | ||
while (offset < source.length) { | ||
offset = executeParsers(source, parsers, offset, tokens); | ||
if (offset <= this.offset) { | ||
this.errorHandler(new Error('Parser should not go back.'), { | ||
currentOffset: this.offset, | ||
requestedOffset: offset, | ||
source: this.source | ||
}); | ||
return null; | ||
} | ||
this.inForesee++; | ||
var result = getToken(this, offset); | ||
if (this.inForesee > 0) { | ||
this.inForesee--; | ||
} | ||
return result; | ||
}; | ||
Context.prototype.parse = function(offset) { | ||
var result = []; | ||
offset = parseInt(offset, 10) || 0; | ||
if (offset < 0) { | ||
offset = 0; | ||
} | ||
while (offset < this.source.length) { | ||
this.offset = offset; | ||
var token = getToken(this, offset); | ||
if (_.isObject(token)) { | ||
result.push(token); | ||
offset = token.offset + token.length; | ||
} | ||
} | ||
return tokens; | ||
}; | ||
return result; | ||
}; | ||
function factory(source, parsers, options) { | ||
options = _.extend({}, options); | ||
options.foreseeLimit = parseInt(options.foreseeLimit, 10) || 0; | ||
if (options.foreseeLimit < 1) { | ||
options.foreseeLimit = 1; | ||
} | ||
return new Context(source, parsers, options); | ||
} | ||
module.exports = factory; |
@@ -5,7 +5,8 @@ 'use strict'; | ||
var defaults = require('../defaults'); | ||
var rendering = require('./index'); | ||
var utils = require('../utils'); | ||
var defaultOptions = { | ||
// Also options for `pattern` renderer | ||
weave: defaults.weave.serge | ||
weave: defaults.weave.serge, | ||
defaultColors: null, | ||
transformSyntaxTree: null | ||
}; | ||
@@ -117,21 +118,11 @@ | ||
function preparePattern(pattern, weave) { | ||
pattern = _.filter(pattern, function(item) { | ||
return _.isArray(item) && (item.length >= 2) && (item[1] > 0); | ||
}); | ||
function preparePattern(node, weave, colors, defaultColors) { | ||
var items = _.isObject(node) && node.isBlock ? node.items : []; | ||
var pattern = utils.sett.compile(items, colors, defaultColors); | ||
var metrics = utils.sett.getPatternMetrics(pattern, weave); | ||
var lengthOfPattern = _.reduce(pattern, function(result, item) { | ||
return result + item[1]; | ||
}, 0); | ||
var weaveLength = _.sum(weave); | ||
var lengthOfCycle = lengthOfPattern; | ||
while (lengthOfCycle % weaveLength != 0) { | ||
lengthOfCycle += lengthOfPattern; | ||
} | ||
return { | ||
pattern: pattern, | ||
lengthOfPattern: lengthOfPattern, | ||
lengthOfCycle: lengthOfCycle | ||
lengthOfPattern: metrics.length, | ||
lengthOfCycle: metrics.fullCycle | ||
}; | ||
@@ -167,6 +158,2 @@ } | ||
function chooseFirstArray() { | ||
return _.find(arguments, _.isArray) || []; | ||
} | ||
function getMetrics(weave, preparedWarp, preparedWeft) { | ||
@@ -200,12 +187,18 @@ return { | ||
} | ||
sett = rendering.pattern(options)(sett); | ||
options = _.extend({}, defaultOptions, options); | ||
if (_.isFunction(options.transformSyntaxTree)) { | ||
sett = options.transformSyntaxTree(sett); | ||
} | ||
var warpIsSameAsWeft = sett.weft === sett.warp; | ||
options = _.extend({}, defaultOptions, options); | ||
var weave = prepareWeave(options.weave, defaults.weave.serge); | ||
var warp = preparePattern(chooseFirstArray(sett.warp, sett.weft), weave); | ||
var warp = preparePattern(sett.warp || sett.weft, weave, | ||
sett.colors, options.defaultColors); | ||
var weft = warp; | ||
if (!warpIsSameAsWeft) { | ||
weft = preparePattern(chooseFirstArray(sett.weft, sett.warp), weave); | ||
weft = preparePattern(sett.weft || sett.warp, weave, | ||
sett.colors, options.defaultColors); | ||
} | ||
@@ -212,0 +205,0 @@ |
'use strict'; | ||
var _ = require('lodash'); | ||
var utils = require('../utils'); | ||
var defaultOptions = { | ||
// function to transform newly built AST: (sett) => { return modifiedSett; } | ||
transformSett: null, | ||
formatters: { | ||
color: function(token) { | ||
return token.name + token.color + ';'; | ||
transformSyntaxTree: null, | ||
format: { | ||
color: function(item) { | ||
var comment = item.comment != '' ? ' ' + item.comment : ''; | ||
return item.name + item.value + comment + ';'; | ||
}, | ||
stripe: function(token) { | ||
return token.name + token.count; | ||
stripe: function(item) { | ||
return item.name + item.count; | ||
}, | ||
pivot: function(token) { | ||
return token.name + '/' + token.count; | ||
block: function(block) { | ||
var result = _.chain(block.formattedItems).join(' ').trim().value(); | ||
return result != '' ? '[' + result + ']' : ''; | ||
} | ||
}, | ||
defaultFormatter: function(token) { | ||
return token.value; | ||
join: function(components) { | ||
var parts = []; | ||
if (components.colors.length > 0) { | ||
parts.push(components.colors.join(' ')); | ||
} | ||
if (components.warp != components.weft) { | ||
parts.push(components.warp + ' // ' + components.weft); | ||
} else { | ||
parts.push(components.warp); | ||
} | ||
return parts.join('\n'); | ||
}, | ||
prepareNestedBlock: function(nestedBlock) { | ||
var result = []; | ||
result.push(utils.newTokenOpeningSquareBracket()); | ||
result = result.concat(nestedBlock); | ||
result.push(utils.newTokenClosingSquareBracket()); | ||
return result; | ||
}, | ||
prepareRootBlock: function(block) { | ||
return block; | ||
}, | ||
joinComponents: function(formattedSett, originalSett) { | ||
return utils.trim([ | ||
formattedSett.colors, | ||
formattedSett.warp, | ||
formattedSett.weft | ||
].join('\n')); | ||
}, | ||
defaultColors: {}, | ||
outputOnlyUsedColors: false | ||
includeUnusedColors: true, | ||
includeDefaultColors: true | ||
}; | ||
function getOnlyUsedColors(tokens, colors, result) { | ||
if (!_.isObject(result)) { | ||
result = {}; | ||
function processColors(usedColors, settColors, options) { | ||
var defaultColors = _.extend({}, options.defaultColors); | ||
var keys = _.intersection(_.keys(settColors), _.keys(usedColors)); | ||
if (options.includeUnusedColors) { | ||
keys = _.keys(settColors); | ||
} | ||
_.each(tokens, function(token) { | ||
if (_.isArray(token)) { | ||
result = getOnlyUsedColors(token, colors, result); | ||
} | ||
if (utils.isStripe(token) || utils.isPivot(token)) { | ||
if (colors[token.name]) { | ||
result[token.name] = colors[token.name]; | ||
} | ||
} | ||
}); | ||
return result; | ||
} | ||
if (options.includeDefaultColors) { | ||
keys = _.union(_.keys(settColors), _.keys(usedColors)); | ||
} | ||
function colorsAsTokens(colors, colorComments, options) { | ||
colorComments = _.extend({}, colorComments); | ||
return _.chain(utils.normalizeColorMap(colors)) | ||
.map(function(value, name) { | ||
var result = utils.newTokenColor(name, value); | ||
var key = name + value; | ||
if (colorComments[key]) { | ||
result.comment = colorComments[key]; | ||
var format = options.format; | ||
if (!_.isFunction(format.color)) { | ||
return []; | ||
} | ||
return _.chain(keys) | ||
.sortBy() | ||
.map(function(key) { | ||
var color = settColors[key] || defaultColors[key]; | ||
if (color) { | ||
return format.color(_.extend({name: key}, color)); | ||
} | ||
return result; | ||
return null; | ||
}) | ||
.sortBy('name') | ||
.filter(function(str) { | ||
return _.isString(str) && (str.length > 0); | ||
}) | ||
.value(); | ||
} | ||
function renderTokens(tokens, options) { | ||
return utils.trim(_.chain(tokens) | ||
.map(function(token) { | ||
var formatter = options.formatters[token.type]; | ||
if (!_.isFunction(formatter)) { | ||
formatter = options.defaultFormatter; | ||
function process(block, options, usedColors) { | ||
var format = options.format; | ||
if (!_.isFunction(format.stripe) || !_.isFunction(format.block)) { | ||
return ''; | ||
} | ||
block = _.clone(block); | ||
block.formattedItems = _.chain(block.items) | ||
.map(function(item) { | ||
if (item.isStripe) { | ||
usedColors[item.name] = true; | ||
return format.stripe(item); | ||
} | ||
return formatter(token); | ||
if (item.isBlock) { | ||
return process(item, options, usedColors); | ||
} | ||
return ''; | ||
}) | ||
.filter() | ||
.join(' ') | ||
/* eslint-disable no-useless-escape */ | ||
.replace(/\[\s/ig, '[') | ||
.replace(/\s\]/ig, ']') | ||
/* eslint-enable no-useless-escape */ | ||
.value()); | ||
.filter(function(str) { | ||
return str.length > 0; | ||
}) | ||
.value(); | ||
return _.isFunction(format.block) ? format.block(block) : ''; | ||
} | ||
function flattenTokens(tokens, options, isNested) { | ||
var result = []; | ||
var current; | ||
if (!isNested) { | ||
tokens = options.prepareRootBlock(tokens); | ||
} | ||
for (var i = 0; i < tokens.length; i++) { | ||
current = tokens[i]; | ||
if (_.isArray(current)) { | ||
// Flatten nested block | ||
current = options.prepareNestedBlock(current); | ||
current = flattenTokens(current, options, true); | ||
[].push.apply(result, current); | ||
} else { | ||
result.push(current); | ||
} | ||
} | ||
return result; | ||
} | ||
function render(sett, options) { | ||
var warpIsSameAsWeft = sett.warp === sett.weft; | ||
var warp = flattenTokens(sett.warp, options); | ||
var usedColors = {}; | ||
var warp = process(sett.warp, options, usedColors); | ||
var weft = warp; | ||
if (!warpIsSameAsWeft) { | ||
weft = flattenTokens(sett.weft, options); | ||
weft = process(sett.weft, options, usedColors); | ||
} | ||
var colors = _.extend({}, options.defaultColors, sett.colors); | ||
if (options.outputOnlyUsedColors) { | ||
colors = _.extend({}, | ||
getOnlyUsedColors(sett.warp, colors), | ||
getOnlyUsedColors(sett.weft, colors) | ||
); | ||
} | ||
colors = colorsAsTokens(colors, sett.colorComments, options); | ||
var colors = processColors(usedColors, sett.colors, options); | ||
colors = renderTokens(colors, options); | ||
warp = renderTokens(warp, options); | ||
weft = renderTokens(weft, options); | ||
if (weft == warp) { | ||
weft = ''; | ||
} | ||
return utils.trim(options.joinComponents({ | ||
return _.trim(options.join({ | ||
colors: colors, | ||
@@ -152,24 +114,2 @@ warp: warp, | ||
options = _.merge({}, defaultOptions, options); | ||
if (!_.isFunction(options.defaultFormatter)) { | ||
options.defaultFormatter = defaultOptions.defaultFormatter; | ||
} | ||
if (!_.isFunction(options.joinComponents)) { | ||
options.joinComponents = defaultOptions.joinComponents; | ||
} | ||
if (!_.isFunction(options.prepareNestedBlock)) { | ||
options.prepareNestedBlock = defaultOptions.prepareNestedBlock; | ||
} | ||
if (!_.isFunction(options.prepareRootBlock)) { | ||
options.prepareRootBlock = defaultOptions.prepareRootBlock; | ||
} | ||
if (!_.isObject(options.formatters)) { | ||
options.formatters = {}; | ||
} | ||
options.formatters = _.chain(options.formatters) | ||
.map(function(value, key) { | ||
return _.isFunction(value) ? [key, value] : null; | ||
}) | ||
.filter() | ||
.fromPairs() | ||
.value(); | ||
@@ -180,4 +120,4 @@ return function(sett) { | ||
} | ||
if (_.isFunction(options.transformSett)) { | ||
sett = options.transformSett(sett); | ||
if (_.isFunction(options.transformSyntaxTree)) { | ||
sett = options.transformSyntaxTree(sett); | ||
} | ||
@@ -184,0 +124,0 @@ |
'use strict'; | ||
module.exports.canvas = require('./canvas'); | ||
module.exports.pattern = require('./pattern'); | ||
module.exports.format = require('./format'); | ||
module.exports.metrics = require('./metrics'); |
'use strict'; | ||
var _ = require('lodash'); | ||
var index = require('./index'); | ||
var render = require('../../render'); | ||
var transform = require('../../transform'); | ||
var utils = require('../../utils'); | ||
var defaults = require('../../defaults'); | ||
function formatPivot(str) { | ||
return str.replace(/^([a-z]+)([0-9]+)$/i, '$1/$2'); | ||
} | ||
var defaultOptions = { | ||
format: { | ||
color: function(item) { | ||
var comment = item.comment != '' ? ' ' + item.comment : ''; | ||
return item.name + item.value + comment + ';'; | ||
}, | ||
stripe: function(item) { | ||
return item.name + item.count; | ||
}, | ||
block: function(block) { | ||
var items = block.formattedItems; | ||
if (block.reflect && (items.length >= 2)) { | ||
// Convert first and last to pivots | ||
items[0] = formatPivot(items[0]); | ||
items[items.length - 1] = formatPivot(items[items.length - 1]); | ||
} | ||
return _.chain(items).join(' ').trim().value(); | ||
} | ||
} | ||
}; | ||
// Options same as for tartan.render.format(): | ||
// + warpAndWeftSeparator: index.warpAndWeftSeparator | ||
// - format | ||
// - join | ||
function factory(options) { | ||
options = _.extend({}, options); | ||
options.transformSett = transform([ | ||
options.transformSett, | ||
transform.flatten(options), | ||
options = _.extend({}, options, defaultOptions); | ||
if (!_.isString(options.warpAndWeftSeparator)) { | ||
options.warpAndWeftSeparator = ''; | ||
} | ||
if (options.warpAndWeftSeparator == '') { | ||
options.warpAndWeftSeparator = index.warpAndWeftSeparator; | ||
} | ||
options.transformSyntaxTree = transform([ | ||
options.transformSyntaxTree, | ||
transform.flatten(), | ||
transform.fold() | ||
]); | ||
options.formatters = { | ||
color: function(token) { | ||
var comment = _.isString(token.comment) && (token.comment.length > 0) ? | ||
' ' + utils.trim(token.comment) : ''; | ||
return token.name + token.color + comment + ';'; | ||
}, | ||
stripe: function(token) { | ||
return token.name + token.count; | ||
}, | ||
pivot: function(token) { | ||
return token.name + '/' + token.count; | ||
options.join = function(components) { | ||
var parts = []; | ||
if (components.colors.length > 0) { | ||
parts.push(components.colors.join(' ')); | ||
} | ||
}; | ||
options.prepareNestedBlock = function(nestedBlock) { | ||
var result = []; | ||
result.push(utils.stripeToPivot(_.first(nestedBlock))); | ||
if (nestedBlock.length > 2) { | ||
result = result.concat(nestedBlock.slice(1, nestedBlock.length - 1)); | ||
if (components.warp != components.weft) { | ||
parts.push(components.warp + ' ' + options.warpAndWeftSeparator + | ||
' ' + components.weft); | ||
} else { | ||
parts.push(components.warp); | ||
} | ||
result.push(utils.stripeToPivot(_.last(nestedBlock))); | ||
return result; | ||
return parts.join('\n'); | ||
}; | ||
options.prepareRootBlock = function(block) { | ||
return block; | ||
}; | ||
options.joinComponents = function(formattedSett, originalSett) { | ||
var threadcount = formattedSett.warp; | ||
var weft = formattedSett.weft; | ||
if ((weft != '') && (weft != formattedSett.warp)) { | ||
threadcount += ' // ' + formattedSett.weft; | ||
} | ||
return utils.trim([formattedSett.colors, threadcount].join('\n')); | ||
}; | ||
return render.format(options); | ||
@@ -49,0 +69,0 @@ } |
@@ -10,1 +10,2 @@ 'use strict'; | ||
module.exports.colors = defaults.colors; | ||
module.exports.warpAndWeftSeparator = defaults.warpAndWeftSeparator; |
'use strict'; | ||
var _ = require('lodash'); | ||
var index = require('./index'); | ||
var defaults = require('../../defaults'); | ||
@@ -11,36 +12,54 @@ var parse = require('../../parse'); | ||
// Options for: tartan.parse() + `transformSett` for `buildSyntaxTree` | ||
/* | ||
options = { | ||
warpAndWeftSeparator: index.warpAndWeftSeparator, | ||
errorHandler: <default>, | ||
processTokens: <default>, | ||
transformSyntaxTree: <default> | ||
} | ||
*/ | ||
function factory(options) { | ||
options = _.extend({}, options); | ||
options.buildSyntaxTree = syntax.default({ | ||
filterTokens: filter.removeTokens(defaults.insignificantTokens), | ||
isWarpAndWeftSeparator: function(token) { | ||
return utils.isLiteral(token) && (token.value == '//'); | ||
}, | ||
transformSett: transform([ | ||
options.transformSett, | ||
transform.checkClassicSyntax() | ||
]) | ||
}); | ||
if (!_.isString(options.warpAndWeftSeparator)) { | ||
options.warpAndWeftSeparator = ''; | ||
} | ||
if (options.warpAndWeftSeparator == '') { | ||
options.warpAndWeftSeparator = index.warpAndWeftSeparator; | ||
} | ||
return parse([ | ||
parse.stripe(_.extend({}, options, { | ||
allowLongNames: true | ||
})), | ||
parse.pivot(), | ||
parse.stripe(), | ||
parse.literal(options.warpAndWeftSeparator), | ||
parse.color({ | ||
allowLongNames: true, | ||
valueAssignment: 'allow', | ||
colorPrefix: 'require', | ||
colorPrefix: /[=]?[#]/, | ||
colorSuffix: null, | ||
colorFormat: 'long', | ||
comment: 'allow', | ||
whitespaceBeforeComment: 'allow', | ||
semicolonAtTheEnd: 'require' | ||
}), | ||
parse.literal('//'), | ||
parse.pivot(_.extend({}, options, { | ||
allowLongNames: true | ||
})) | ||
], options); | ||
allowComment: true, | ||
commentSuffix: /;/, | ||
requireCommentSuffix: true, | ||
commentFormat: /^\s*(.*)\s*;\s*$/ | ||
}) | ||
], { | ||
errorHandler: options.errorHandler, | ||
processTokens: filter([ | ||
options.processTokens, | ||
filter.removeTokens(defaults.insignificantTokens) | ||
]), | ||
buildSyntaxTree: syntax.classic({ | ||
errorHandler: options.errorHandler, | ||
processTokens: filter.classify({ | ||
isWarpAndWeftSeparator: function(token) { | ||
return utils.token.isLiteral(token) && | ||
(token.value == options.warpAndWeftSeparator); | ||
} | ||
}), | ||
transformSyntaxTree: options.transformSyntaxTree | ||
}) | ||
}); | ||
} | ||
module.exports = factory; |
'use strict'; | ||
module.exports.default = require('./default'); | ||
module.exports.classic = require('./classic'); | ||
module.exports.extended = require('./extended'); | ||
module.exports.stwr = require('./stwr'); | ||
module.exports.weddslist = require('./weddslist'); |
'use strict'; | ||
var _ = require('lodash'); | ||
var index = require('./index'); | ||
var render = require('../../render'); | ||
var transform = require('../../transform'); | ||
var utils = require('../../utils'); | ||
var defaults = require('../../defaults'); | ||
function formatPivot(str) { | ||
return str.replace(/^([a-z]+)([0-9]+)$/i, '$1/$2'); | ||
} | ||
var defaultOptions = { | ||
format: { | ||
color: function(item) { | ||
var comment = item.comment != '' ? ' ' + item.comment : ''; | ||
return item.name + '=' + item.value + comment + ';'; | ||
}, | ||
stripe: function(item) { | ||
return item.name + item.count; | ||
}, | ||
block: function(block) { | ||
var items = block.formattedItems; | ||
if (block.reflect && (items.length >= 2)) { | ||
// Convert first and last to pivots | ||
items[0] = formatPivot(items[0]); | ||
items[items.length - 1] = formatPivot(items[items.length - 1]); | ||
} | ||
return _.chain(items).join(' ').trim().value(); | ||
} | ||
} | ||
}; | ||
// Options same as for tartan.render.format(): | ||
// + warpAndWeftSeparator: index.warpAndWeftSeparator | ||
// - format | ||
// - join | ||
function factory(options) { | ||
options = _.extend({}, options); | ||
options.transformSett = transform([ | ||
options.transformSett, | ||
transform.flatten(options), | ||
options = _.extend({}, options, defaultOptions); | ||
if (!_.isString(options.warpAndWeftSeparator)) { | ||
options.warpAndWeftSeparator = ''; | ||
} | ||
if (options.warpAndWeftSeparator == '') { | ||
options.warpAndWeftSeparator = index.warpAndWeftSeparator; | ||
} | ||
options.transformSyntaxTree = transform([ | ||
options.transformSyntaxTree, | ||
transform.flatten(), | ||
transform.fold() | ||
]); | ||
options.formatters = { | ||
color: function(token) { | ||
var color = token.color; | ||
color = color.substr(1, color.length).toUpperCase(); | ||
var comment = _.isString(token.comment) && (token.comment.length > 0) ? | ||
' ' + utils.trim(token.comment) : ''; | ||
return token.name + '=' + color + comment + ';'; | ||
}, | ||
stripe: function(token) { | ||
return token.name + token.count; | ||
}, | ||
pivot: function(token) { | ||
return token.name + '/' + token.count; | ||
options.join = function(components) { | ||
var parts = []; | ||
if (components.colors.length > 0) { | ||
parts.push(components.colors.join(' ')); | ||
} | ||
}; | ||
options.prepareNestedBlock = function(nestedBlock) { | ||
var result = []; | ||
result.push(utils.stripeToPivot(_.first(nestedBlock))); | ||
if (nestedBlock.length > 2) { | ||
result = result.concat(nestedBlock.slice(1, nestedBlock.length - 1)); | ||
if (components.warp != components.weft) { | ||
parts.push(components.warp + ' ' + options.warpAndWeftSeparator + | ||
' ' + components.weft); | ||
} else { | ||
parts.push(components.warp); | ||
} | ||
result.push(utils.stripeToPivot(_.last(nestedBlock))); | ||
return result; | ||
return parts.join('\n'); | ||
}; | ||
options.prepareRootBlock = function(block) { | ||
return block; | ||
}; | ||
options.joinComponents = function(formattedSett, originalSett) { | ||
var threadcount = formattedSett.warp; | ||
var weft = formattedSett.weft; | ||
if ((weft != '') && (weft != formattedSett.warp)) { | ||
threadcount += ' . ' + formattedSett.weft; | ||
} | ||
return utils.trim([formattedSett.colors, threadcount].join('\n')); | ||
}; | ||
return render.format(options); | ||
@@ -51,0 +69,0 @@ } |
'use strict'; | ||
var utils = require('../../utils'); | ||
module.exports.id = 'stwr'; | ||
@@ -8,3 +10,3 @@ module.exports.name = 'Scottish Register of Tartans / ' + | ||
module.exports.format = require('./format'); | ||
module.exports.colors = { | ||
module.exports.colors = utils.color.buildColorMap({ | ||
/* eslint-disable key-spacing */ | ||
@@ -23,2 +25,3 @@ K: '#000000', LP: '#9966ff', P: '#9933ff', | ||
/* eslint-enable key-spacing */ | ||
}; | ||
}); | ||
module.exports.warpAndWeftSeparator = '.'; |
'use strict'; | ||
var _ = require('lodash'); | ||
var index = require('./index'); | ||
var defaults = require('../../defaults'); | ||
@@ -11,36 +12,54 @@ var parse = require('../../parse'); | ||
// Options for: tartan.parse() + `transformSett` for `buildSyntaxTree` | ||
/* | ||
options = { | ||
warpAndWeftSeparator: index.warpAndWeftSeparator | ||
errorHandler: <default>, | ||
processTokens: <default>, | ||
transformSyntaxTree: <default> | ||
} | ||
*/ | ||
function factory(options) { | ||
options = _.extend({}, options); | ||
options.buildSyntaxTree = syntax.default({ | ||
filterTokens: filter.removeTokens(defaults.insignificantTokens), | ||
isWarpAndWeftSeparator: function(token) { | ||
return utils.isLiteral(token) && (token.value == '.'); | ||
}, | ||
transformSett: transform([ | ||
options.transformSett, | ||
transform.checkClassicSyntax() | ||
]) | ||
}); | ||
if (!_.isString(options.warpAndWeftSeparator)) { | ||
options.warpAndWeftSeparator = ''; | ||
} | ||
if (options.warpAndWeftSeparator == '') { | ||
options.warpAndWeftSeparator = index.warpAndWeftSeparator; | ||
} | ||
return parse([ | ||
parse.stripe(_.extend({}, options, { | ||
allowLongNames: true | ||
})), | ||
parse.pivot(), | ||
parse.stripe(), | ||
parse.literal(options.warpAndWeftSeparator), | ||
parse.color({ | ||
allowLongNames: true, | ||
valueAssignment: 'require', | ||
colorPrefix: 'allow', | ||
colorPrefix: /[=][#]?/, | ||
colorSuffix: null, | ||
colorFormat: 'long', | ||
comment: 'allow', | ||
whitespaceBeforeComment: 'allow', | ||
semicolonAtTheEnd: 'require' | ||
}), | ||
parse.literal('.'), | ||
parse.pivot(_.extend({}, options, { | ||
allowLongNames: true | ||
})) | ||
], options); | ||
allowComment: true, | ||
commentSuffix: /;/, | ||
requireCommentSuffix: true, | ||
commentFormat: /^\s*(.*)\s*;\s*$/ | ||
}) | ||
], { | ||
errorHandler: options.errorHandler, | ||
processTokens: filter([ | ||
options.processTokens, | ||
filter.removeTokens(defaults.insignificantTokens) | ||
]), | ||
buildSyntaxTree: syntax.classic({ | ||
errorHandler: options.errorHandler, | ||
processTokens: filter.classify({ | ||
isWarpAndWeftSeparator: function(token) { | ||
return utils.token.isLiteral(token) && | ||
(token.value == options.warpAndWeftSeparator); | ||
} | ||
}), | ||
transformSyntaxTree: options.transformSyntaxTree | ||
}) | ||
}); | ||
} | ||
module.exports = factory; |
'use strict'; | ||
var _ = require('lodash'); | ||
var index = require('./index'); | ||
var render = require('../../render'); | ||
var transform = require('../../transform'); | ||
var utils = require('../../utils'); | ||
var defaults = require('../../defaults'); | ||
function factory(options) { | ||
options = _.extend({}, options); | ||
options.transformSett = transform([ | ||
options.transformSett, | ||
transform.flatten(options), | ||
transform.fold() | ||
]); | ||
options.formatters = { | ||
color: function(token) { | ||
return token.name + token.color; | ||
function formatPivot(str) { | ||
return str.replace(/^([a-z]+)([0-9]+)$/i, '$1/$2'); | ||
} | ||
var defaultOptions = { | ||
format: { | ||
color: function(item) { | ||
return item.name + item.value; | ||
}, | ||
stripe: function(token) { | ||
return token.name + token.count; | ||
stripe: function(item) { | ||
return item.name + item.count; | ||
}, | ||
block: function(block) { | ||
var items = block.formattedItems; | ||
if (block.reflect && (items.length >= 3)) { | ||
items.splice(1, 0, '('); | ||
items.splice(-1, 0, ')'); | ||
} | ||
return _.chain(items).join(' ').trim().value() | ||
.replace(/\(\s+/g, '(') | ||
.replace(/\s+\)/g, ')'); | ||
} | ||
}; | ||
options.prepareNestedBlock = function(nestedBlock) { | ||
var result = _.clone(nestedBlock); | ||
if (nestedBlock.length > 2) { | ||
result.splice(1, 0, utils.newTokenOpeningParenthesis()); | ||
result.splice(-1, 0, utils.newTokenClosingParenthesis()); | ||
}, | ||
join: function(components) { | ||
var parts = []; | ||
if (components.colors.length > 0) { | ||
parts.push(components.colors.join(' ')); | ||
} | ||
return result; | ||
}; | ||
options.prepareRootBlock = function(block) { | ||
var result = _.clone(block); | ||
if ((result.length == 1) && _.isArray(result[0])) { | ||
return result; | ||
var warp = components.warp; | ||
var weft = components.weft; | ||
if (warp == '') { | ||
warp = weft; | ||
weft = ''; | ||
} | ||
result.splice(0, 0, utils.newTokenOpeningParenthesis()); | ||
result.push(utils.newTokenClosingParenthesis()); | ||
return result; | ||
}; | ||
options.joinComponents = function(formattedSett, originalSett) { | ||
var warp = '[ ' + formattedSett.warp | ||
.replace(/\(\s/g, '(').replace(/\s\)/g, ')'); | ||
var weft = ''; | ||
if (formattedSett.weft != formattedSett.warp) { | ||
if (formattedSett.weft != '') { | ||
weft = '] ' + formattedSett.weft | ||
.replace(/\(\s/g, '(').replace(/\s\)/g, ')'); | ||
} | ||
if (components.warp == components.weft) { | ||
weft = ''; | ||
} | ||
return utils.trim([formattedSett.colors, warp, weft].join('\n')); | ||
}; | ||
if (warp != '') { | ||
parts.push('[ ' + warp); | ||
} | ||
if (weft != '') { | ||
parts.push('] ' + weft); | ||
} | ||
return parts.join('\n'); | ||
} | ||
}; | ||
// Options same as for tartan.render.format(): | ||
// - format | ||
// - join | ||
function factory(options) { | ||
options = _.extend({}, options, defaultOptions); | ||
options.transformSyntaxTree = transform([ | ||
options.transformSyntaxTree, | ||
transform.flatten(), | ||
transform.fold() | ||
]); | ||
return render.format(options); | ||
@@ -54,0 +72,0 @@ } |
'use strict'; | ||
var utils = require('../../utils'); | ||
module.exports.id = 'weddslist'; | ||
@@ -8,3 +10,3 @@ module.exports.name = 'Syntax by Weddslist (TDF)'; | ||
module.exports.colors = { | ||
module.exports.colors = utils.color.buildColorMap({ | ||
/* eslint-disable key-spacing */ | ||
@@ -22,2 +24,2 @@ W: '#ffffff', TR: '#ffffe9', R: '#800000', | ||
/* eslint-enable key-spacing */ | ||
}; | ||
}); |
@@ -9,33 +9,62 @@ 'use strict'; | ||
var transform = require('../../transform'); | ||
var utils = require('../../utils'); | ||
// Options for: tartan.parse() + `transformSett` for `buildSyntaxTree` | ||
/* | ||
options = { | ||
errorHandler: <default>, | ||
processTokens: <default>, | ||
transformSyntaxTree: <default> | ||
} | ||
*/ | ||
function factory(options) { | ||
options = _.extend({}, options); | ||
options.buildSyntaxTree = syntax.weddslist({ | ||
filterTokens: filter.removeTokens(defaults.insignificantTokens), | ||
transformSett: transform([ | ||
options.transformSett, | ||
transform.checkClassicSyntax() | ||
]) | ||
}); | ||
return parse([ | ||
parse.stripe(_.extend({}, options, { | ||
allowLongNames: true | ||
})), | ||
parse.color(_.extend({}, options, { | ||
parse.stripe(), | ||
parse.literal('('), | ||
parse.literal(')'), | ||
parse.literal('['), | ||
parse.literal(']'), | ||
parse.color({ | ||
allowLongNames: true, | ||
valueAssignment: 'none', | ||
colorPrefix: 'require', | ||
colorPrefix: /[#]/, | ||
colorSuffix: null, | ||
colorFormat: 'long', | ||
comment: 'none', | ||
semicolonAtTheEnd: 'allow' | ||
})), | ||
parse.literal('['), | ||
parse.literal(']'), | ||
parse.literal('('), | ||
parse.literal(')') | ||
], options); | ||
allowComment: false | ||
}) | ||
], { | ||
errorHandler: options.errorHandler, | ||
processTokens: filter([ | ||
options.processTokens, | ||
filter.removeTokens(defaults.insignificantTokens) | ||
]), | ||
buildSyntaxTree: syntax.weddslist({ | ||
errorHandler: options.errorHandler, | ||
processTokens: filter.classify({ | ||
// Disable some token classes | ||
isWarpAndWeftSeparator: null, | ||
isPivot: null, | ||
isBlockStart: null, | ||
isBlockEnd: null, | ||
// Add new token classes | ||
isWarpStart: function(token) { | ||
return utils.token.isLiteral(token) && (token.value == '['); | ||
}, | ||
isWeftStart: function(token) { | ||
return utils.token.isLiteral(token) && (token.value == ']'); | ||
}, | ||
isBlockBodyStart: function(token) { | ||
return utils.token.isLiteral(token) && (token.value == '('); | ||
}, | ||
isBlockBodyEnd: function(token) { | ||
return utils.token.isLiteral(token) && (token.value == ')'); | ||
} | ||
}), | ||
transformSyntaxTree: options.transformSyntaxTree | ||
}) | ||
}); | ||
} | ||
module.exports = factory; |
'use strict'; | ||
module.exports.default = require('./default'); | ||
module.exports.extended = require('./extended'); | ||
module.exports.classic = require('./classic'); | ||
module.exports.weddslist = require('./weddslist'); |
@@ -5,209 +5,167 @@ 'use strict'; | ||
var utils = require('../utils'); | ||
var errors = require('../errors'); | ||
var defaultOptions = { | ||
// Error handler | ||
errorHandler: function(error, data, severity) { | ||
// Do nothing | ||
}, | ||
// function to filter parsed tokens: (tokens) => { return modifiedTokens; } | ||
filterTokens: null, | ||
// Fail on invalid tokens; tokens outside warp and weft; multiple warp | ||
// and weft delimiters, etc. | ||
failOnMalformedSequence: true, | ||
// function to transform newly built AST: (sett) => { return modifiedSett; } | ||
transformSett: null | ||
processTokens: null, | ||
// function to transform newly built AST: (ast) => { return modifiedAst; } | ||
transformSyntaxTree: null | ||
}; | ||
function splitWarpAndWeft(tokens, options) { | ||
var colors = []; | ||
var warp = null; | ||
var weft = null; | ||
var currentBlock = 'colors'; | ||
var i; | ||
var token; | ||
/* | ||
<sett> ::= [ { <color> } ] | ||
<sequence> | | ||
<warp> [ <weft> ] | | ||
[ <warp> ] <weft> | | ||
<weft> <warp> | ||
for (i = 0; i < tokens.length; i++) { | ||
token = tokens[i]; | ||
switch (currentBlock) { | ||
case 'colors': { | ||
if (utils.isColor(token)) { | ||
colors.push(token); | ||
continue; | ||
} | ||
if (utils.isLiteral(token)) { | ||
switch (token.value) { | ||
case '[': | ||
if (!warp) { | ||
warp = []; | ||
currentBlock = 'warp'; | ||
continue; | ||
} | ||
break; | ||
case ']': | ||
if (!weft) { | ||
weft = []; | ||
currentBlock = 'weft'; | ||
continue; | ||
} | ||
break; | ||
default: break; | ||
} | ||
} | ||
break; | ||
<warp> ::= '[' <sequence> | ||
<weft> ::= ']' <sequence> | ||
<sequence> ::= <reflected> | <repetitive> | ||
<reflected> ::= <stripe> '(' { <stripe> } ')' <stripe> | ||
<repetitive> ::= [ '(' ] { <stripe> } [ ')' ] | ||
*/ | ||
function buildTree(tokens, options) { | ||
var first; | ||
var last; | ||
var isReflected = false; | ||
tokens = _.filter(tokens, function(token) { | ||
return !token.isWarpStart && !token.isWeftStart; | ||
}); | ||
// Strip parenthesis at beginning and end | ||
if (tokens.length >= 2) { | ||
first = _.first(tokens); | ||
last = _.last(tokens); | ||
if (first.isBlockBodyStart && last.isBlockBodyEnd) { | ||
tokens.splice(0, 1); | ||
tokens.splice(-1, 1); | ||
} else { | ||
if (first.isBlockBodyStart) { | ||
options.errorHandler( | ||
new Error(utils.error.message.unexpectedToken), | ||
{token: first}, | ||
utils.error.severity.error | ||
); | ||
tokens.splice(0, 1); | ||
} | ||
case 'warp': { | ||
if (utils.isStripe(token)) { | ||
warp.push(token); | ||
continue; | ||
} | ||
if (utils.isLiteral(token)) { | ||
switch (token.value) { | ||
case '(': | ||
warp.push(token); | ||
continue; | ||
case ')': | ||
warp.push(token); | ||
continue; | ||
case ']': | ||
if (!weft) { | ||
weft = []; | ||
currentBlock = 'weft'; | ||
continue; | ||
} | ||
break; | ||
default: break; | ||
} | ||
} | ||
break; | ||
if (last.isBlockBodyStart) { | ||
options.errorHandler( | ||
new Error(utils.error.message.unexpectedToken), | ||
{token: last}, | ||
utils.error.severity.error | ||
); | ||
tokens.splice(-1, 1); | ||
} | ||
case 'weft': { | ||
if (utils.isStripe(token)) { | ||
weft.push(token); | ||
continue; | ||
} | ||
if (utils.isLiteral(token)) { | ||
switch (token.value) { | ||
case '(': | ||
weft.push(token); | ||
continue; | ||
case ')': | ||
weft.push(token); | ||
continue; | ||
case '[': | ||
if (!warp) { | ||
warp = []; | ||
currentBlock = 'warp'; | ||
continue; | ||
} | ||
break; | ||
default: break; | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
// Check if sequence is reflected | ||
if (tokens.length >= 4) { | ||
first = _.first(tokens); | ||
last = _.last(tokens); | ||
if (first.isStripe && last.isStripe) { | ||
first = tokens[1]; | ||
last = tokens[tokens.length - 2]; | ||
if (first.isBlockBodyStart && last.isBlockBodyEnd) { | ||
isReflected = true; | ||
tokens.splice(1, 1); | ||
tokens.splice(-2, 1); | ||
} | ||
default: | ||
break; | ||
} | ||
// If we are here - we were unable to process token, so trigger error | ||
if (options.failOnMalformedSequence) { | ||
throw new errors.InvalidToken(token); | ||
} | ||
} | ||
var result = { | ||
colors: colors, | ||
warp: warp || [], | ||
weft: weft || [] | ||
}; | ||
// Convert all tokens to items | ||
var items = _.chain(tokens) | ||
.map(function(token) { | ||
if (token.isStripe) { | ||
return utils.node.newStripe(token); | ||
} | ||
options.errorHandler( | ||
new Error(utils.error.message.unexpectedToken), | ||
{token: token}, | ||
utils.error.severity.error | ||
); | ||
return null; | ||
}) | ||
.filter() | ||
.value(); | ||
if (result.warp.length == 0) { | ||
result.warp = result.weft; | ||
// Check for <stripe> '(' ')' <stripe> | ||
if (items.length <= 2) { | ||
isReflected = false; | ||
} | ||
if (result.weft.length == 0) { | ||
result.weft = result.warp; | ||
} | ||
return result; | ||
return utils.node.newRootBlock(items, isReflected); | ||
} | ||
function buildColorMap(tokens) { | ||
var result = {}; | ||
function extractSequence(tokens, result, options, shouldBreak) { | ||
var first = _.first(tokens); | ||
_.each(tokens, function(token) { | ||
result[token.name] = token.color; | ||
if (shouldBreak(token)) { | ||
return false; // Break | ||
} | ||
if (token.isWarpStart && (token !== first)) { | ||
options.errorHandler( | ||
new Error(utils.error.message.multipleWarpAnWeftSeparator), | ||
{token: token}, | ||
utils.error.severity.warning | ||
); | ||
} | ||
result.push(token); | ||
}); | ||
return result; | ||
} | ||
function checkParenthesisSyntax(tokens, options) { | ||
function extractWarpAndWeft(tokens, warp, weft, options) { | ||
if (tokens.length == 0) { | ||
return true; | ||
return; | ||
} | ||
var openingCount = 0; | ||
var closingCount = 0; | ||
var first; | ||
var isWarpExtracted = false; | ||
_.each(tokens, function(token) { | ||
if (utils.isOpeningParenthesis(token)) { | ||
if ((openingCount == 1) && options.failOnMalformedSequence) { | ||
throw new errors.InvalidToken(token); | ||
} | ||
openingCount++; | ||
} | ||
if (utils.isClosingParenthesis(token)) { | ||
if ((closingCount == 1) && options.failOnMalformedSequence) { | ||
throw new errors.InvalidToken(token); | ||
} | ||
closingCount++; | ||
} | ||
}); | ||
if ((openingCount > 1) || (closingCount > 1)) { | ||
return false; | ||
// Try to extract warp | ||
first = _.first(tokens); | ||
if (first.isWarpStart || first.isStripe || first.isBlockBodyStart) { | ||
extractSequence(tokens, warp, options, function(token) { | ||
return token.isWeftStart; | ||
}); | ||
tokens.splice(0, warp.length); | ||
isWarpExtracted = true; | ||
} | ||
if ( | ||
utils.isOpeningParenthesis(_.first(tokens)) && | ||
utils.isClosingParenthesis(_.last(tokens)) | ||
) { | ||
return true; | ||
} | ||
if (tokens.length >= 4) { | ||
if ( | ||
utils.isOpeningParenthesis(tokens[1]) && | ||
utils.isClosingParenthesis(tokens[tokens.length - 2]) | ||
) { | ||
return true; | ||
} | ||
} | ||
if (options.failOnMalformedSequence) { | ||
var token = _.findLast(tokens, utils.isLiteral); | ||
if (!token) { | ||
// Hm. We have no parenthesis at all | ||
return true; | ||
} | ||
throw new errors.InvalidToken(token); | ||
} | ||
return false; | ||
} | ||
function buildTree(tokens, options) { | ||
var result = _.clone(tokens); | ||
if (!checkParenthesisSyntax(result, options)) { | ||
result = _.filter(result, function(token) { | ||
return !utils.isLiteral(token); | ||
// Try to extract weft | ||
first = _.first(tokens); | ||
if (first && first.isWeftStart) { | ||
extractSequence(tokens, weft, options, function(token) { | ||
return token.isWarpStart; | ||
}); | ||
tokens.splice(0, weft.length); | ||
} | ||
if (result.length >= 2) { | ||
var isReflective = utils.isOpeningParenthesis(result[1]); | ||
result = _.filter(result, function(token) { | ||
return !utils.isLiteral(token); | ||
}); | ||
if (isReflective && (result.length > 0)) { | ||
result = [result]; | ||
// If warp was not extracted, try again, but more strict | ||
if (!isWarpExtracted) { | ||
first = _.first(tokens); | ||
if (first && first.isWarpStart) { | ||
extractSequence(tokens, warp, options, function(token) { | ||
return token.isWeftStart; | ||
}); | ||
tokens.splice(0, warp.length); | ||
} | ||
} | ||
return result; | ||
// Trigger error for rest tokens | ||
_.each(tokens, function(token) { | ||
options.errorHandler( | ||
new Error(utils.error.message.extraTokenInInputSequence), | ||
{token: token}, | ||
utils.error.severity.warning | ||
); | ||
}); | ||
} | ||
@@ -220,4 +178,4 @@ | ||
} | ||
if (_.isFunction(options.filterTokens)) { | ||
tokens = options.filterTokens(tokens); | ||
if (_.isFunction(options.processTokens)) { | ||
tokens = options.processTokens(tokens); | ||
if (!_.isArray(tokens)) { | ||
@@ -228,19 +186,31 @@ return tokens; | ||
tokens = splitWarpAndWeft(tokens, options); | ||
var warpIsSameAsWeft = tokens.warp === tokens.weft; | ||
// Extract colors; split warp and weft | ||
var colorTokens = _.filter(tokens, function(token) { | ||
return token.isColor; | ||
}); | ||
var warpTokens = []; | ||
var weftTokens = []; | ||
extractWarpAndWeft(_.filter(tokens, function(token) { | ||
return !token.isColor; | ||
}), warpTokens, weftTokens, options); | ||
if (warpTokens.length == 0) { | ||
warpTokens = weftTokens; | ||
weftTokens = []; | ||
} | ||
if (weftTokens.length == 0) { | ||
weftTokens = warpTokens; | ||
} | ||
var result = {}; | ||
result.colors = buildColorMap(tokens.colors); | ||
result.warp = buildTree(tokens.warp, options); | ||
if (warpIsSameAsWeft) { | ||
result.colors = utils.color.buildColorMap(colorTokens); | ||
result.warp = buildTree(warpTokens, options); | ||
if (weftTokens === warpTokens) { | ||
result.weft = result.warp; | ||
} else { | ||
result.weft = buildTree(tokens.weft, options); | ||
result.weft = buildTree(weftTokens, options); | ||
} | ||
if (_.isFunction(options.transformSett)) { | ||
result = options.transformSett(result); | ||
if (_.isFunction(options.transformSyntaxTree)) { | ||
result = options.transformSyntaxTree(result); | ||
} | ||
return result; | ||
@@ -251,2 +221,5 @@ } | ||
options = _.extend({}, defaultOptions, options); | ||
if (!_.isFunction(options.errorHandler)) { | ||
options.errorHandler = defaultOptions.errorHandler; | ||
} | ||
return function(tokens) { | ||
@@ -253,0 +226,0 @@ return buildSyntaxTree(tokens, options); |
'use strict'; | ||
var _ = require('lodash'); | ||
var mergeStripes = require('./merge-stripes'); | ||
var utils = require('../utils'); | ||
var defaultOptions = { | ||
mergeStripes: true | ||
}; | ||
function flatten(tokens, isNested) { | ||
function flatten(block) { | ||
var result = []; | ||
var current; | ||
for (var i = 0; i < tokens.length; i++) { | ||
current = tokens[i]; | ||
if (_.isArray(current)) { | ||
// Flatten nested blocks | ||
_.each(block.items, function(item) { | ||
if (item.isBlock) { | ||
// Flatten nested block | ||
current = flatten(current, true); | ||
[].push.apply(result, current); | ||
item = flatten(item); | ||
[].push.apply(result, item.items); | ||
} else { | ||
result.push(current); | ||
result.push(item); | ||
} | ||
} | ||
}); | ||
// If we are flattening nested block, we need to reflect it | ||
// Do not reflect blocks with single stripe | ||
if (isNested && (result.length > 1)) { | ||
var rest = result.slice(0, result.length - 1); | ||
rest.reverse(); | ||
result = result.concat(rest); | ||
} | ||
// Special case. | ||
// All nested blocks should be reflected relative to the last pivot; | ||
// first pivot is duplicated. | ||
// But if entire threadcount should be reflected, algorithm a bit differs: | ||
// R/10 K20 Y10 W/2 should become R10 K20 Y10 W2 Y10 K20 - without last R20. | ||
// So let's check this case: | ||
if (!isNested && (tokens.length == 1) && _.isArray(tokens[0])) { | ||
result.pop(); | ||
} | ||
return result; | ||
// Reflect and repeat | ||
block = _.clone(block); | ||
block.items = result; | ||
return utils.sett.reflectAndRepeat(block); | ||
} | ||
function transform(sett, options) { | ||
function transform(sett) { | ||
var result = _.clone(sett); | ||
var warpIsSameAsWeft = sett.warp == sett.weft; | ||
if (_.isArray(sett.warp)) { | ||
if (_.isObject(sett.warp)) { | ||
result.warp = flatten(sett.warp); | ||
} | ||
if (_.isArray(sett.weft)) { | ||
if (_.isObject(sett.weft)) { | ||
if (warpIsSameAsWeft) { | ||
@@ -61,16 +41,9 @@ result.weft = result.warp; | ||
if (options.mergeStripes) { | ||
result = mergeStripes()(result); | ||
} | ||
return result; | ||
} | ||
function factory(options) { | ||
options = _.extend({}, defaultOptions, options); | ||
return function(sett) { | ||
return transform(sett, options); | ||
}; | ||
function factory() { | ||
return transform; | ||
} | ||
module.exports = factory; |
'use strict'; | ||
var _ = require('lodash'); | ||
var utils = require('../utils'); | ||
function processTokens(tokens) { | ||
if (tokens.length % 2 != 0) { | ||
return tokens; | ||
var defaultOptions = { | ||
}; | ||
function processTokens(root, options) { | ||
if (root.reflect || (root.items.length % 2 != 0)) { | ||
return root; | ||
} | ||
@@ -13,4 +15,4 @@ // Smallest reflective sett contains 3 stripes in threadcount or | ||
// R/10 K/2 => R10 K2 | ||
if (tokens.length < 4) { | ||
return tokens; | ||
if (root.items.length < 4) { | ||
return root; | ||
} | ||
@@ -20,11 +22,11 @@ | ||
var i = 1; | ||
var j = tokens.length - 1; | ||
var j = root.items.length - 1; | ||
var left; | ||
var right; | ||
result.push(tokens[0]); | ||
result.push(root.items[0]); | ||
while (true) { | ||
left = tokens[i]; | ||
right = tokens[j]; | ||
if (utils.isStripe(left) && (utils.isStripe(right))) { | ||
left = root.items[i]; | ||
right = root.items[j]; | ||
if (left.isStripe && right.isStripe) { | ||
var isSameColor = left.name == right.name; | ||
@@ -46,3 +48,8 @@ var isSameCount = left.count == right.count; | ||
return result ? [result] : tokens; | ||
if (result) { | ||
root = _.clone(root); | ||
root.items = result; | ||
root.reflect = true; | ||
} | ||
return root; | ||
} | ||
@@ -54,6 +61,6 @@ | ||
var warpIsSameAsWeft = sett.warp === sett.weft; | ||
if (_.isArray(sett.warp)) { | ||
if (_.isObject(sett.warp)) { | ||
result.warp = processTokens(sett.warp, options); | ||
} | ||
if (_.isArray(sett.weft)) { | ||
if (_.isObject(sett.weft)) { | ||
if (warpIsSameAsWeft) { | ||
@@ -69,6 +76,9 @@ result.weft = result.warp; | ||
function factory() { | ||
return transform; | ||
function factory(options) { | ||
options = _.extend({}, defaultOptions, options); | ||
return function(sett) { | ||
return transform(sett, options); | ||
}; | ||
} | ||
module.exports = factory; |
@@ -10,5 +10,5 @@ 'use strict'; | ||
if (_.isObject(sett)) { | ||
for (var i = 0; i < processors.length; i++) { | ||
sett = processors[i](sett); | ||
} | ||
_.each(processors, function(processor) { | ||
sett = processor(sett); | ||
}); | ||
} | ||
@@ -21,6 +21,8 @@ return sett; | ||
module.exports.checkClassicSyntax = require('./check-classic-syntax'); | ||
module.exports.flatten = require('./flatten'); | ||
module.exports.flattenSimpleBlocks = require('./flatten-simple-blocks'); | ||
module.exports.fold = require('./fold'); | ||
module.exports.mergeStripes = require('./merge-stripes'); | ||
module.exports.removeEmptyBlocks = require('./remove-empty-blocks'); | ||
module.exports.removeZeroWidthStripes = require('./remove-zero-width-stripes'); | ||
module.exports.optimize = require('./optimize'); |
'use strict'; | ||
var _ = require('lodash'); | ||
var utils = require('../utils'); | ||
// Do not merge first and last stripe in the only nested block in a root | ||
// as they are pivots! For other nested blocks, first pivot can be merged. | ||
// Do not merge last stripe in reflected blocks as it is central pivot. | ||
// Do not merge first stripe in blocks if it is reflected and repeated. | ||
// Example: [R20 R10 R5 Y2 K10 K5] | ||
@@ -13,49 +12,37 @@ // Wrong: [R35 Y2 K15] => R35 Y2 K15 Y2 | ||
// => R35 Y2 K25 Y2 R15 | ||
function processTokens(tokens, isNested, doNotMergeFirstPivot) { | ||
var result = []; | ||
function processTokens(block) { | ||
// Root is always repetitive | ||
var first = block.reflect && (block.isRoot || (block.repeat > 1)) ? | ||
_.first(block.items) : null; | ||
var last = block.reflect ? _.last(block.items) : null; | ||
// Special case | ||
if (!isNested && (tokens.length == 1) && (_.isArray(tokens[0]))) { | ||
result.push(processTokens(tokens[0], true, true)); | ||
return result; | ||
} | ||
var token; | ||
var prev; | ||
var correctionStart = doNotMergeFirstPivot ? 1 : 0; | ||
var correctionEnd = isNested ? 1 : 0; | ||
for (var i = correctionStart; i < tokens.length - correctionEnd; i++) { | ||
token = tokens[i]; | ||
if (_.isArray(token)) { | ||
result.push(processTokens(token, true)); | ||
continue; | ||
block = _.clone(block); | ||
block.items = _.reduce(block.items, function(accumulator, item) { | ||
// Process nested blocks | ||
if (item.isBlock) { | ||
accumulator.push(processTokens(item)); | ||
return accumulator; | ||
} | ||
if (utils.isStripe(token)) { | ||
prev = _.last(result); | ||
// If current stripe is the same as previous - merge them | ||
if (utils.isStripe(prev) && (prev.name == token.name)) { | ||
prev = _.clone(result.pop()); | ||
prev.count += token.count; | ||
result.push(prev); | ||
continue; | ||
if (item.isStripe) { | ||
// Check last item | ||
if (item === last) { | ||
accumulator.push(item); | ||
return accumulator; | ||
} | ||
var prev = _.last(accumulator); | ||
// Check first item | ||
if (prev && prev.isStripe && (prev !== first)) { | ||
if (prev.name == item.name) { | ||
prev = _.clone(accumulator.pop()); | ||
prev.count += item.count; | ||
accumulator.push(prev); | ||
return accumulator; | ||
} | ||
} | ||
} | ||
result.push(token); | ||
} | ||
accumulator.push(item); | ||
return accumulator; | ||
}, []); | ||
// For nested blocks, keep first (depending on doNotMergeFirstPivot) | ||
// and last stripe | ||
if (isNested) { | ||
token = _.first(tokens); | ||
if (token && doNotMergeFirstPivot) { | ||
result.splice(0, 0, token); | ||
} | ||
token = _.last(tokens); | ||
if (token && (tokens.length > 1)) { | ||
result.push(token); | ||
} | ||
} | ||
return result; | ||
return block; | ||
} | ||
@@ -67,6 +54,6 @@ | ||
var warpIsSameAsWeft = sett.warp === sett.weft; | ||
if (_.isArray(sett.warp)) { | ||
if (_.isObject(sett.warp)) { | ||
result.warp = processTokens(sett.warp, options); | ||
} | ||
if (_.isArray(sett.weft)) { | ||
if (_.isObject(sett.weft)) { | ||
if (warpIsSameAsWeft) { | ||
@@ -73,0 +60,0 @@ result.weft = result.warp; |
'use strict'; | ||
var _ = require('lodash'); | ||
var utils = require('../utils'); | ||
var flattenSimpleBlocks = require('./flatten-simple-blocks'); | ||
var mergeStripes = require('./merge-stripes'); | ||
var removeEmptyBlocks = require('./remove-empty-blocks'); | ||
var removeZeroWidthStripes = require('./remove-zero-width-stripes'); | ||
var mergeStripes = require('./merge-stripes'); | ||
var defaultOptions = { | ||
removeZeroWidthStripes: true, | ||
removeEmptyBlocks: true, | ||
mergeStripes: true, | ||
unfoldSingleColorBlocks: true | ||
// Also options for removeZeroWidthStripes | ||
simplifyBlocks: true, | ||
simplifyStripes: true | ||
}; | ||
function removeEmptyBlocks(tokens) { | ||
var result = []; | ||
var token; | ||
for (var i = 0; i < tokens.length; i++) { | ||
token = tokens[i]; | ||
if (_.isArray(token)) { | ||
// Double-check length to avoid unnecessary recursion | ||
if (token.length > 0) { | ||
token = removeEmptyBlocks(token); | ||
if (token.length > 0) { | ||
result.push(token); | ||
} | ||
} | ||
} else { | ||
// Keep everything else | ||
result.push(token); | ||
} | ||
} | ||
return result; | ||
} | ||
function optimize(sett, options) { | ||
// Empty blocks anyway should be removed | ||
sett = removeEmptyBlocks()(sett); | ||
// [R20] => R20 | ||
// [R20 R10 R5] => R20 R10 R5 R10 => R65 | ||
function unfoldSingleColorBlocks(tokens, isNested, doNotMergeFirstPivot) { | ||
if (tokens.length == 0) { | ||
return tokens; | ||
if (options.simplifyBlocks) { | ||
// Try to unfold simple blocks; it may produce new stripes | ||
// instead of blocks, so do it first | ||
sett = flattenSimpleBlocks()(sett); | ||
} | ||
// Special case | ||
if (!isNested) { | ||
doNotMergeFirstPivot = (tokens.length == 1) && (_.isArray(tokens[0])); | ||
} | ||
// Zero-width stripes also should be removed | ||
sett = removeZeroWidthStripes(options)(sett); | ||
var result = _.clone(tokens); // We will edit it in-place | ||
var token; | ||
var firstToken = null; | ||
var i; | ||
// Process nested blocks first | ||
for (i = 0; i < result.length; i++) { | ||
token = result[i]; | ||
// Process nested blocks | ||
if (_.isArray(token)) { | ||
token = unfoldSingleColorBlocks(token, true, doNotMergeFirstPivot); | ||
result[i] = token.length != 1 ? token : _.first(token); | ||
} | ||
if (options.simplifyStripes) { | ||
sett = mergeStripes()(sett); | ||
} | ||
// Check input array | ||
for (i = 0; i < result.length; i++) { | ||
token = result[i]; | ||
if (!utils.isStripe(token)) { | ||
return result; | ||
} | ||
if (firstToken) { | ||
if (token.name != firstToken.name) { | ||
return result; | ||
} | ||
} else { | ||
firstToken = token; | ||
} | ||
} | ||
// If we are here - we have all tokens with the same color | ||
// For nested blocks, add each color twice (except of the first and last one) | ||
// as they will be duplicated after reflecting | ||
var multiplier = isNested ? 2 : 1; | ||
var count = _.first(result).count; | ||
if (!doNotMergeFirstPivot) { | ||
count *= multiplier; | ||
} | ||
for (i = 1; i < result.length - 1; i++) { | ||
count += multiplier * result[i].count; | ||
} | ||
if (result.length > 1) { | ||
// Avoid adding first element twice if there is the only stripe | ||
count += _.last(result).count; | ||
} | ||
result = utils.newTokenStripe(firstToken.name, count); | ||
return isNested ? result : [result]; | ||
return sett; | ||
} | ||
function processTokens(tokens, options) { | ||
// Remove empty blocks | ||
if (options.removeEmptyBlocks) { | ||
tokens = removeEmptyBlocks(tokens); | ||
} | ||
// Then simplify blocks that contains only single-color stripes | ||
if (options.unfoldSingleColorBlocks) { | ||
tokens = unfoldSingleColorBlocks(tokens); | ||
} | ||
return tokens; | ||
} | ||
function transform(sett, options) { | ||
var result = _.clone(sett); | ||
// First of all, remove zero-width stripes. It may create empty blocks | ||
if (options.removeZeroWidthStripes) { | ||
result = removeZeroWidthStripes(options)(result); | ||
} | ||
var warpIsSameAsWeft = result.warp === result.weft; | ||
if (_.isArray(result.warp)) { | ||
result.warp = processTokens(result.warp, options); | ||
} | ||
if (_.isArray(result.weft)) { | ||
if (warpIsSameAsWeft) { | ||
result.weft = result.warp; | ||
} else { | ||
result.weft = processTokens(result.weft, options); | ||
} | ||
} | ||
if (options.mergeStripes) { | ||
result = mergeStripes()(result); | ||
} | ||
return result; | ||
} | ||
function factory(options) { | ||
options = _.extend({}, defaultOptions, options); | ||
return function(sett) { | ||
return transform(sett, options); | ||
return optimize(sett, options); | ||
}; | ||
@@ -143,0 +41,0 @@ } |
'use strict'; | ||
var _ = require('lodash'); | ||
var utils = require('../utils'); | ||
// TODO: Last zero-width stripe in block can be removed... | ||
// ... with modifying previous stripe: | ||
// [R10 K4 W0] => R10 K4 W0 K4 R10 => R10 K8 R10 | ||
// [R10 K8] => R10 K8 R10 | ||
var defaultOptions = { | ||
@@ -10,31 +14,44 @@ keepZeroWidthPivots: true | ||
function removeZeroWidthStripes(tokens, options, isNested) { | ||
var result = []; | ||
var token; | ||
var first = _.first(tokens); | ||
var last = _.last(tokens); | ||
for (var i = 0; i < tokens.length; i++) { | ||
token = tokens[i]; | ||
// Recursive processing of nested blocks | ||
if (_.isArray(token)) { | ||
result.push(removeZeroWidthStripes(token, options, true)); | ||
} else | ||
// Check stripes | ||
if (utils.isStripe(token)) { | ||
if (token.count > 0) { | ||
result.push(token); | ||
} else { | ||
// Do not remove last stripe in reflected blocks as it is central pivot. | ||
// Do not remove first stripe in blocks if it is reflected and repeated. | ||
// Example: [R0 B10 Y2 K5] | ||
// Wrong: [B10 Y2 K5] => B10 Y2 K5 Y2 | ||
// Right: [R0 B10 Y2 K5] | ||
// => R0 B10 Y2 K5 Y2 B10 | ||
// => B10 Y2 K5 Y2 B10 | ||
function removeZeroWidthStripes(block, options) { | ||
// Root is always repetitive | ||
var first = block.reflect && (block.isRoot || (block.repeat > 1)) ? | ||
_.first(block.items) : null; | ||
var last = block.reflect ? _.last(block.items) : null; | ||
block = _.clone(block); | ||
block.items = _.chain(block.items) | ||
.map(function(item) { | ||
// Recursive processing of nested blocks | ||
if (item.isBlock) { | ||
item = removeZeroWidthStripes(item, options); | ||
return item.items.length > 0 ? item : null; | ||
} else | ||
// Check stripes | ||
if (item.isStripe) { | ||
if (item.count > 0) { | ||
return item; | ||
} | ||
if (options.keepZeroWidthPivots) { | ||
// For nested blocks, keep first and last stripe as they are pivots | ||
if (isNested && ((token === first) || (token === last))) { | ||
result.push(token); | ||
// Keep first and last stripes as they are pivots | ||
if ((item === first) || (item === last)) { | ||
return item; | ||
} | ||
} | ||
} else { | ||
// Keep everything else | ||
return item; | ||
} | ||
} else { | ||
// Keep everything else | ||
result.push(token); | ||
} | ||
} | ||
return result; | ||
return null; | ||
}) | ||
.filter() | ||
.value(); | ||
return block; | ||
} | ||
@@ -46,6 +63,6 @@ | ||
if (_.isArray(sett.warp)) { | ||
if (_.isObject(sett.warp)) { | ||
result.warp = removeZeroWidthStripes(sett.warp, options); | ||
} | ||
if (_.isArray(sett.weft)) { | ||
if (_.isObject(sett.weft)) { | ||
if (warpIsSameAsWeft) { | ||
@@ -52,0 +69,0 @@ result.weft = result.warp; |
'use strict'; | ||
var _ = require('lodash'); | ||
var errors = require('../errors'); | ||
var TokenType = { | ||
invalid: 'invalid', | ||
whitespace: 'whitespace', | ||
color: 'color', | ||
stripe: 'stripe', | ||
pivot: 'pivot', | ||
literal: 'literal' | ||
}; | ||
function trim(str) { | ||
if (!_.isString(str)) { | ||
return str; | ||
} | ||
return str.replace(/^\s+/i, '').replace(/\s+$/i, ''); | ||
} | ||
function isValidName(str) { | ||
return /^[a-z]+$/i.test(str); | ||
} | ||
function isValidColor(str, acceptShortFormat) { | ||
if (acceptShortFormat) { | ||
return /^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(str); | ||
} | ||
return /^#[0-9a-f]{6}$/i.test(str); | ||
} | ||
function normalizeColor(str) { | ||
if (/^#[0-9a-f]{6}$/i.test(str)) { | ||
return str.toLowerCase(); | ||
} | ||
if (/^#[0-9a-f]{3}$/i.test(str)) { | ||
// Duplicate each hexadecimal character | ||
return str.replace(/[0-9a-f]/ig, '$&$&').toLowerCase(); | ||
} | ||
return false; | ||
} | ||
function normalizeColorMap(colors) { | ||
var result = {}; | ||
_.each(colors, function(value, name) { | ||
name = isValidName(name) ? name : null; | ||
value = normalizeColor(value); | ||
if (name && value) { | ||
result[name.toUpperCase()] = value; | ||
} | ||
}); | ||
return result; | ||
} | ||
function isToken(token, type) { | ||
return _.isObject(token) && (token.type == type); | ||
} | ||
function isInvalid(token) { | ||
return isToken(token, TokenType.invalid); | ||
} | ||
function isWhitespace(token) { | ||
return isToken(token, TokenType.whitespace); | ||
} | ||
function isColor(token) { | ||
return isToken(token, TokenType.color); | ||
} | ||
function isStripe(token) { | ||
return isToken(token, TokenType.stripe); | ||
} | ||
function isPivot(token) { | ||
return isToken(token, TokenType.pivot); | ||
} | ||
function isLiteral(token) { | ||
return isToken(token, TokenType.literal); | ||
} | ||
function isSquareBracket(token) { | ||
return isLiteral(token) && ((token.value == '[') || (token.value == ']')); | ||
} | ||
function isOpeningSquareBracket(token) { | ||
return isLiteral(token) && (token.value == '['); | ||
} | ||
function isClosingSquareBracket(token) { | ||
return isLiteral(token) && (token.value == ']'); | ||
} | ||
function isParenthesis(token) { | ||
return isLiteral(token) && ((token.value == '(') || (token.value == ')')); | ||
} | ||
function isOpeningParenthesis(token) { | ||
return isLiteral(token) && (token.value == '('); | ||
} | ||
function isClosingParenthesis(token) { | ||
return isLiteral(token) && (token.value == ')'); | ||
} | ||
function pivotToStripe(token) { | ||
if (isPivot(token)) { | ||
token = _.clone(token); | ||
token.type = TokenType.stripe; | ||
} | ||
return token; | ||
} | ||
function stripeToPivot(token) { | ||
if (isStripe(token)) { | ||
token = _.clone(token); | ||
token.type = TokenType.pivot; | ||
} | ||
return token; | ||
} | ||
function newToken(type, value) { | ||
var result = { | ||
type: type, | ||
source: '', | ||
offset: -1, | ||
length: -1 | ||
}; | ||
if (_.isString(value)) { | ||
result.value = value; | ||
result.length = value.length; | ||
} | ||
return result; | ||
} | ||
function newTokenInvalid(value) { | ||
return newToken(TokenType.invalid, value); | ||
} | ||
function newTokenWhitespace(value) { | ||
return newToken(TokenType.whitespace, value); | ||
} | ||
function newTokenColor(name, value) { | ||
if (!isValidName(name)) { | ||
throw new errors.CreateTokenError('Invalid color name ' + | ||
JSON.stringify(name)); | ||
} | ||
if (!isValidColor(value)) { | ||
throw new errors.CreateTokenError('Invalid color ' + JSON.stringify(name)); | ||
} | ||
var result = newToken(TokenType.color); | ||
result.name = name; | ||
result.color = value; | ||
return result; | ||
} | ||
function newTokenStripe(name, count) { | ||
if (!isValidName(name)) { | ||
throw new errors.CreateTokenError('Invalid color name ' + | ||
JSON.stringify(name)); | ||
} | ||
count = parseInt(count, 10) || 0; | ||
if (count < 0) { | ||
throw new errors.CreateTokenError('Count of threads should be >= 0'); | ||
} | ||
var result = newToken(TokenType.stripe); | ||
result.name = name; | ||
result.count = count; | ||
return result; | ||
} | ||
function newTokenPivot(name, count) { | ||
if (!isValidName(name)) { | ||
throw new errors.CreateTokenError('Invalid color name ' + | ||
JSON.stringify(name)); | ||
} | ||
count = parseInt(count, 10) || 0; | ||
if (count < 0) { | ||
throw new errors.CreateTokenError('Count of threads should be >= 0'); | ||
} | ||
var result = newToken(TokenType.pivot); | ||
result.name = name; | ||
result.count = count; | ||
return result; | ||
} | ||
function newTokenSquareBracket(value) { | ||
if ((value != '[') && (value != ']')) { | ||
throw new errors.CreateTokenError('Invalid value ' + JSON.stringify(value)); | ||
} | ||
return newToken(TokenType.literal, value); | ||
} | ||
function newTokenOpeningSquareBracket() { | ||
return newToken(TokenType.literal, '['); | ||
} | ||
function newTokenClosingSquareBracket() { | ||
return newToken(TokenType.literal, ']'); | ||
} | ||
function newTokenParenthesis(value) { | ||
if ((value != '(') && (value != ')')) { | ||
throw new errors.CreateTokenError('Invalid value ' + JSON.stringify(value)); | ||
} | ||
return newToken(TokenType.literal, value); | ||
} | ||
function newTokenOpeningParenthesis() { | ||
return newToken(TokenType.literal, '('); | ||
} | ||
function newTokenClosingParenthesis() { | ||
return newToken(TokenType.literal, ')'); | ||
} | ||
function newTokenLiteral(value) { | ||
return newToken(TokenType.literal, value); | ||
} | ||
module.exports.TokenType = TokenType; | ||
module.exports.trim = trim; | ||
module.exports.isValidName = isValidName; | ||
module.exports.isValidColor = isValidColor; | ||
module.exports.normalizeColor = normalizeColor; | ||
module.exports.normalizeColorMap = normalizeColorMap; | ||
module.exports.isToken = isToken; | ||
module.exports.isInvalid = isInvalid; | ||
module.exports.isWhitespace = isWhitespace; | ||
module.exports.isColor = isColor; | ||
module.exports.isStripe = isStripe; | ||
module.exports.isPivot = isPivot; | ||
module.exports.isSquareBracket = isSquareBracket; | ||
module.exports.isOpeningSquareBracket = isOpeningSquareBracket; | ||
module.exports.isClosingSquareBracket = isClosingSquareBracket; | ||
module.exports.isParenthesis = isParenthesis; | ||
module.exports.isOpeningParenthesis = isOpeningParenthesis; | ||
module.exports.isClosingParenthesis = isClosingParenthesis; | ||
module.exports.isLiteral = isLiteral; | ||
module.exports.pivotToStripe = pivotToStripe; | ||
module.exports.stripeToPivot = stripeToPivot; | ||
module.exports.newToken = newToken; | ||
module.exports.newTokenInvalid = newTokenInvalid; | ||
module.exports.newTokenWhitespace = newTokenWhitespace; | ||
module.exports.newTokenColor = newTokenColor; | ||
module.exports.newTokenStripe = newTokenStripe; | ||
module.exports.newTokenPivot = newTokenPivot; | ||
module.exports.newTokenSquareBracket = newTokenSquareBracket; | ||
module.exports.newTokenOpeningSquareBracket = newTokenOpeningSquareBracket; | ||
module.exports.newTokenClosingSquareBracket = newTokenClosingSquareBracket; | ||
module.exports.newTokenParenthesis = newTokenParenthesis; | ||
module.exports.newTokenOpeningParenthesis = newTokenOpeningParenthesis; | ||
module.exports.newTokenClosingParenthesis = newTokenClosingParenthesis; | ||
module.exports.newTokenLiteral = newTokenLiteral; | ||
module.exports.error = require('./error'); | ||
module.exports.color = require('./color'); | ||
module.exports.token = require('./token'); | ||
module.exports.node = require('./node'); | ||
module.exports.sett = require('./sett'); | ||
module.exports.repaint = require('./repaint'); |
'use strict'; | ||
var assert = require('chai').assert; | ||
var packageInfo = require('../package.json'); | ||
var tartan = require('../src'); | ||
describe('Stub', function() { | ||
describe('Core', function() { | ||
it('Should check library interface', function(done) { | ||
assert.equal(tartan.version, packageInfo.version); | ||
assert(tartan.parse, 'Parser should be exported'); | ||
assert(tartan.filter, 'Filters should be exported'); | ||
assert(tartan.syntax, 'AST Builders should be exported'); | ||
assert(tartan.transform, 'AST Transformations should be exported'); | ||
assert(tartan.render, 'Renderers should be exported'); | ||
assert(tartan.defaults, 'Defaults should be exported'); | ||
it('Should pass', function(done) { | ||
done(); | ||
@@ -20,0 +7,0 @@ }); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3131755
69
22282