minor refactoring and tweaking...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-12-18 00:17:21 +03:00
parent 6ecc5bf150
commit 7722b881ff

View File

@ -20,6 +20,12 @@
* - will it be faster? * - will it be faster?
* *
* *
* XXX should we .optimizeTags(tag) on .tag(tag)???
* XXX should this serialize recursively down???
* ...it might be a good idea to decide on a serialization
* protocol and use it throughout...
*
*
* *
**********************************************************************/ **********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
@ -50,6 +56,17 @@ var normalizeSplit = function(args){
/*********************************************************************/ /*********************************************************************/
var BaseTagsClassPrototype = { var BaseTagsClassPrototype = {
PATH_SEPARATOR: /[\\\/]+/g,
SET_SEPARATOR: /:+/g,
COMBINED_SEPARATOR: /[:\\\/]+/g,
splitSet: function(str){
return str.split(this.SET_SEPARATOR) },
splitPath: function(str){
return str.split(this.PATH_SEPARATOR) },
// Utils... // Utils...
// //
// .normalize(tag) // .normalize(tag)
@ -85,10 +102,10 @@ var BaseTagsClassPrototype = {
.toLowerCase() .toLowerCase()
.replace(tagRemovedChars, '') .replace(tagRemovedChars, '')
// sort sets within paths... // sort sets within paths...
.split(/[\\\/]+/g) .split(that.PATH_SEPARATOR)
.map(function(e){ .map(function(e){
return e return e
.split(/:+/g) .split(that.SET_SEPARATOR)
// remove empty set members... // remove empty set members...
.filter(function(t){ .filter(function(t){
return t != '' }) return t != '' })
@ -106,9 +123,10 @@ var BaseTagsClassPrototype = {
: res : res
}, },
subTags: function(...tags){ subTags: function(...tags){
var that = this
return this.normalize(normalizeSplit(tags)) return this.normalize(normalizeSplit(tags))
.map(function(tag){ .map(function(tag){
return tag.split(/[:\\\/]/g) }) return tag.split(that.COMBINED_SEPARATOR) })
.flat() .flat()
.unique() }, .unique() },
@ -217,6 +235,11 @@ var BaseTagsPrototype = {
tagRemovedChars: '[\\s-_]', tagRemovedChars: '[\\s-_]',
}, },
PATH_SEPARATOR: BaseTagsClassPrototype.PATH_SEPARATOR,
SET_SEPARATOR: BaseTagsClassPrototype.SET_SEPARATOR,
COMBINED_SEPARATOR: BaseTagsClassPrototype.COMBINED_SEPARATOR,
// Tag index... // Tag index...
// //
// Format: // Format:
@ -239,7 +262,7 @@ var BaseTagsPrototype = {
// //
// Format: // Format:
// { // {
// <single-tag>: <tag-set>, // <tag>: [<tag>, ..],
// ... // ...
// } // }
// //
@ -248,13 +271,28 @@ var BaseTagsPrototype = {
// Utils... // Utils...
// //
// proxies to class methods... // XXX should these be proxies or simply refs???
// - proxies would make it possible to change stuff in the
// constructor and affect all the instances...
// - refs would make things a bit faster but would isolate
// the instances from the constructor...
splitSet: BaseTagsClassPrototype.splitSet,
splitPath: BaseTagsClassPrototype.splitPath,
normalize: BaseTagsClassPrototype.normalize,
subTags: BaseTagsClassPrototype.subTags,
parseQuery: BaseTagsClassPrototype.parseQuery,
/*// proxies to class methods...
splitSet: function(str){
return this.constructor.splitSet.call(this, str) },
splitPath: function(str){
return this.constructor.splitPath.call(this, str) },
normalize: function(...tags){ normalize: function(...tags){
return this.constructor.normalize.call(this, ...tags) }, return this.constructor.normalize.call(this, ...tags) },
subTags: function(...tags){ subTags: function(...tags){
return this.constructor.subTags.call(this, ...tags) }, return this.constructor.subTags.call(this, ...tags) },
parseQuery: function(query){ parseQuery: function(query){
return this.constructor.parseQuery.call(this, query) }, return this.constructor.parseQuery.call(this, query) },
//*/
// Tag matching and filtering... // Tag matching and filtering...
@ -367,14 +405,14 @@ var BaseTagsPrototype = {
// NOTE: this does not care about order... // NOTE: this does not care about order...
// NOTE: this matches single tags too... // NOTE: this matches single tags too...
var matchSet = function(a, b){ var matchSet = function(a, b){
a = a.split(/:/g) a = that.splitSet(a)
b = b.split(/:/g) b = that.splitSet(b)
// extend b with definitions... // extend b with definitions...
b = b b = b
.concat(!definitions ? [] : (b .concat(!definitions ? [] : (b
.map(function(v){ .map(function(v){
return v in definitions ? return v in definitions ?
definitions[v].split(/:/g) definitions[v]
: [] }) : [] })
.flat())) .flat()))
return a.length <= b.length return a.length <= b.length
@ -392,8 +430,8 @@ var BaseTagsPrototype = {
// //
// NOTE: we do not need to use cmp(..) here as we are testing // NOTE: we do not need to use cmp(..) here as we are testing
// tag compatibility deeper in matchSet(..)... // tag compatibility deeper in matchSet(..)...
var sa = a.split(/[\/\\]/g) var sa = that.splitPath(a)
var sb = b.split(/[\/\\]/g) var sb = that.splitPath(b)
return ( return (
// fail if base/root fails... // fail if base/root fails...
(root && !matchSet(sa[0], sb[0]) (root && !matchSet(sa[0], sb[0])
@ -463,7 +501,7 @@ var BaseTagsPrototype = {
return res return res
} }
path = path.split(/[\\\/]/g) path = that.splitPath(path)
// restrict direction... // restrict direction...
path = path.slice(0, path.indexOf(tag)) path = path.slice(0, path.indexOf(tag))
@ -576,7 +614,7 @@ var BaseTagsPrototype = {
this.paths() this.paths()
: this.normalize(normalizeSplit(list))) : this.normalize(normalizeSplit(list)))
// sort by number of path elements (longest first)... // sort by number of path elements (longest first)...
.map(function(p){ return p.split(/[\\\/]/g) }) .map(that.splitPath)
.sort(function(a, b){ return b.length - a.length }) .sort(function(a, b){ return b.length - a.length })
.map(function(p){ return p.join('/') }) .map(function(p){ return p.join('/') })
// remove all paths in tail that match the current... // remove all paths in tail that match the current...
@ -641,7 +679,7 @@ var BaseTagsPrototype = {
// NOTE: this includes all the persistent tags as well as all the // NOTE: this includes all the persistent tags as well as all the
// tags actually used. // tags actually used.
// //
// XXX should this return split values??? // XXX should this return split values / combined with .singleTags(..)???
// i.e. 'red:car' -> ['red', 'car'] // i.e. 'red:car' -> ['red', 'car']
tags: function(value, ...tags){ tags: function(value, ...tags){
var that = this var that = this
@ -675,16 +713,11 @@ var BaseTagsPrototype = {
} }
}, },
// Same as .tags(..) but returns a list of single tags... // Same as .tags(..) but returns a list of single tags...
// XXX should we combine this with .tags(..) ???
singleTags: function(value, ...tags){ singleTags: function(value, ...tags){
return this.subTags(this.tags(...arguments)).unique() }, return this.subTags(this.tags(...arguments)).unique() },
// XXX should this support ...tags???
// XXX should this expand paths???
// ...i.e. show all the paths a value participates in...
paths: function(value){ paths: function(value){
return this.tags(value) return this.tags(value)
.filter(function(tag){ return /[\\\/]/.test(tag) }) }, .filter(function(tag){ return /[\\\/]/.test(tag) }) },
// XXX should this support ...tags???
sets: function(value){ sets: function(value){
return this.tags(value) return this.tags(value)
.filter(function(tag){ return tag.includes(':') }) }, .filter(function(tag){ return tag.includes(':') }) },
@ -712,89 +745,11 @@ var BaseTagsPrototype = {
// Edit API... // Edit API...
// //
// Get/set/remove tag definitions...
//
// A definition is a single tag that is defined by ("means") a tag set.
//
// Resolve definition (recursive)...
// .alias(tag)
// -> value
// -> undefined
//
// Set definition...
// .alias(tag, value)
// -> this
//
// Remove definition...
// .alias(tag, null)
// -> this
//
//
// Example:
// ts.define('birds', 'bird:many')
//
// ts.match('bird', ['bird', 'birds', 'animal']) // -> ['bird', 'birds']
//
//
// XXX revise recursion testing and if it is needed...
define: function(tag, value){
var definitions = this.definitions = this.definitions || {}
// XXX this seems a bit ugly...
var resolve = function(tag, seen){
seen = seen || []
// check for loops...
if(seen.indexOf(tag) >= 0){
throw new Error(`.alias(..): Recursive alias chain: "${
seen
.concat([seen[0]])
.join('" -> "') }"`) }
var next = definitions[tag]
|| definitions[this.normalize(tag)]
seen.push(tag)
return next != null ?
resolve(next, seen)
: seen.length > 1 ?
tag
: undefined
}.bind(this)
tag = this.normalize(tag)
if(/[:\\\/]/.test(tag)){
throw new Error(`.alias(..): tag must be a single tag, got: "${tag}`) }
// resolve...
if(arguments.length == 1){
return resolve(tag)
// remove...
} else if(value == null){
delete definitions[tag]
// set...
} else {
value = this.normalize(value)
if(/[\\\/]/.test(tag)){
throw new Error(`.alias(..): value must not be a path, got: "${value}`) }
// check for recursion...
var chain = []
var target = resolve(value, chain)
if(target == tag || target == this.normalize(tag)){
throw new Error(`.alias(..): Creating a recursive alias chain: "${
chain
.concat([chain[0]])
.join('" -> "') }"`) }
definitions[tag] = value
}
return this
},
// XXX save un-normalized tags to dict... ??? // XXX save un-normalized tags to dict... ???
tag: function(tags, value){ tag: function(tags, value){
var that = this var that = this
value = value instanceof Array ? value : [value] value = value instanceof Array ? value : [value]
tags = this.normalize(tags instanceof Array ? tags : [tags]) tags = this.normalize(tags)
var index = this.__index = this.__index || {} var index = this.__index = this.__index || {}
tags tags
@ -813,7 +768,7 @@ var BaseTagsPrototype = {
value = value instanceof Array ? value : [value] value = value instanceof Array ? value : [value]
this this
.normalize(tags instanceof Array ? tags : [tags]) .normalize(tags)
// resolve/match tags... // resolve/match tags...
.map(function(tag){ .map(function(tag){
return /\*/.test(tag) ? return /\*/.test(tag) ?
@ -1093,6 +1048,84 @@ var BaseTagsPrototype = {
return res return res
}, },
// Get/set/remove tag definitions...
//
// A definition is a single tag that is defined by ("means") a tag set.
//
// Resolve definition (recursive)...
// .define(tag)
// -> value
// -> undefined
//
// Set definition...
// .define(tag, value)
// -> this
//
// Remove definition...
// .define(tag, null)
// -> this
//
//
// Example:
// ts.define('birds', 'bird:many')
//
// ts.match('bird', ['bird', 'birds', 'animal']) // -> ['bird', 'birds']
//
// XXX revise recursion testing and if it is needed...
define: function(tag, value){
var definitions = this.definitions = this.definitions || {}
// XXX this seems a bit ugly...
var resolve = function(tag, seen){
seen = seen || []
// check for loops...
if(seen.indexOf(tag) >= 0){
throw new Error(`.alias(..): Recursive alias chain: "${
seen
.concat([seen[0]])
.join('" -> "') }"`) }
var next = definitions[tag]
|| definitions[this.normalize(tag)]
seen.push(tag)
return next != null ?
resolve(next, seen)
: seen.length > 1 ?
tag
: undefined
}.bind(this)
tag = this.normalize(tag)
if(/[:\\\/]/.test(tag)){
throw new Error(`.alias(..): tag must be a single tag, got: "${tag}`) }
// resolve...
if(arguments.length == 1){
return resolve(tag)
// remove...
} else if(value == null){
delete definitions[tag]
// set...
} else {
value = this.normalize(value)
if(/[\\\/]/.test(tag)){
throw new Error(`.alias(..): value must not be a path, got: "${value}`) }
// check for recursion...
var chain = []
var target = resolve(value, chain)
if(target == tag || target == this.normalize(tag)){
throw new Error(`.alias(..): Creating a recursive alias chain: "${
chain
.concat([chain[0]])
.join('" -> "') }"`) }
definitions[tag] = this.splitSet(value)
}
return this
},
// Optimize tags... // Optimize tags...
// //
// Optimize tags for all values... // Optimize tags for all values...
@ -1115,15 +1148,6 @@ var BaseTagsPrototype = {
// // fully contained within 'a/b/c'... // // fully contained within 'a/b/c'...
// //
// //
// XXX Q: should this be done on .tag(..) and friends???
// ...currently I do not think so
// + would keep the tags consistent...
// - slow things down on large numbers of tags
// - would seem inconsistent
// .tag('a/c', 'x')
// .tag('a/b/c', 'x')
// .tags(x) // -> ['a/b/c']
// might be good to add this as an option...
optimizeTags: function(...values){ optimizeTags: function(...values){
var that = this var that = this
return (normalizeSplit(values) || this.values()) return (normalizeSplit(values) || this.values())
@ -1229,7 +1253,7 @@ var BaseTagsPrototype = {
// (a b ^(c d) c e) // (a b ^(c d) c e)
// -> (a b c d e) // -> (a b c d e)
// //
// NOTE: that the repeating value 'c' is discarded. // NOTE: the repeating value 'c' is discarded.
// NOTE: @( .. ) can be used to expand values at the pre-processor // NOTE: @( .. ) can be used to expand values at the pre-processor
// stage. // stage.
// //
@ -1355,13 +1379,13 @@ var BaseTagsPrototype = {
// .query(query, true) // .query(query, true)
// -> values // -> values
// //
// XXX do we need expand(..) ???
query: function(query, raw){ query: function(query, raw){
var that = this var that = this
var pre = this.__query_ns_pre var pre = this.__query_ns_pre
var ns = this.__query_ns var ns = this.__query_ns
var sns = this.__query_ns_special var sns = this.__query_ns_special
// Expand prefixed lists into the containing list...
var expand = function(prefix, list){ var expand = function(prefix, list){
return prefix == null ? return prefix == null ?
list list
@ -1372,7 +1396,6 @@ var BaseTagsPrototype = {
: res.concat([e]) }, []) : res.concat([e]) }, [])
.filter(function(e){ .filter(function(e){
return e != prefix }) } return e != prefix }) }
// Query Language pre-processor... // Query Language pre-processor...
var PreQL = function(args){ var PreQL = function(args){
return ( return (
@ -1388,7 +1411,6 @@ var BaseTagsPrototype = {
: arg.startsWith('`') && arg.endsWith('`') ? : arg.startsWith('`') && arg.endsWith('`') ?
PreQL(['search', arg.slice(1, -1)]) PreQL(['search', arg.slice(1, -1)])
: arg }) ) } : arg }) ) }
// Query Language Executor... // Query Language Executor...
var QL = function(args){ var QL = function(args){
return ( return (
@ -1417,8 +1439,7 @@ var BaseTagsPrototype = {
// normalize results by default... // normalize results by default...
: (this : (this
.flat() .flat()
.unique()) }) .unique()) }) },
},
// Object utility API... // Object utility API...
@ -1466,9 +1487,6 @@ var BaseTagsPrototype = {
// //
// NOTE: to get the current tags use .tags() // NOTE: to get the current tags use .tags()
// //
// XXX should this serialize recursively down???
// ...it might be a good idea to decide on a serialization
// protocol and use it throughout...
json: function(){ json: function(){
var res = {} var res = {}
@ -1565,12 +1583,14 @@ var TagsWithHandlersPrototype = {
// ... // ...
// } // }
// //
//__special_tag_handlers__: null, __special_tag_handlers__: null,
/*/ DEBUG...
__special_tag_handlers__: { __special_tag_handlers__: {
//'PrintTag': function(tag, action, ...other){ 'PrintTag': function(tag, action, ...other){
// console.log('TAG:', action, tag, ...other) }, console.log('TAG:', action, tag, ...other) },
//'*': 'PrintTag', '*': 'PrintTag',
}, },
//*/
// Call the matching special tag handlers... // Call the matching special tag handlers...