refactoring and simplification + docs...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-12-28 06:11:23 +03:00
parent 5b7b7f9257
commit 846f9a3b4c

View File

@ -167,25 +167,28 @@ var BaseTagsClassPrototype = {
var res = normalizeSplit(tags) var res = normalizeSplit(tags)
.map(function(tag){ .map(function(tag){
return tag return typeof(tag) == typeof('str') ?
.trim() tag
.toLowerCase() .trim()
.replace(ILLEGAL_CHARS, '') .toLowerCase()
// sort sets within paths... .replace(ILLEGAL_CHARS, '')
.split(PP) // sort sets within paths...
.map(function(e){ .split(PP)
return e .map(function(e){
.split(SP) return e
// remove empty set members... .split(SP)
.filter(function(t){ // remove empty set members...
return t != '' }) .filter(function(t){
.unique() return t != '' })
.sort() .unique()
.join(SS) }) .sort()
// NOTE: this also kills the leading '/' .join(SS) })
.filter(function(t){ // NOTE: this also kills the leading '/'
return t != '' }) .filter(function(t){
.join(PS) }) return t != '' })
.join(PS)
: [] })
.flat()
.unique() .unique()
return (tags.length == 1 && !(tags[0] instanceof Array)) ? return (tags.length == 1 && !(tags[0] instanceof Array)) ?
// NOTE: if we got a single tag return it as a single tag... // NOTE: if we got a single tag return it as a single tag...
@ -402,6 +405,8 @@ var BaseTagsPrototype = {
// a - single tag // a - single tag
// NOTE: a tag is also a unary path and a singular // NOTE: a tag is also a unary path and a singular
// set (see below). // set (see below).
// "a" - explicit match, this checks if the tag exists as-is
// without any additional heuristics...
// a/b - path, 2 or more tags joined with '/' (or '\'). // a/b - path, 2 or more tags joined with '/' (or '\').
// defines a directional relation between a and b // defines a directional relation between a and b
// /a - path special case, a path with a leading '/' (or '\') // /a - path special case, a path with a leading '/' (or '\')
@ -410,6 +415,7 @@ var BaseTagsPrototype = {
// defines a non-directional relation between a and b. // defines a non-directional relation between a and b.
// * - tag placeholder, matches one and only one tag // * - tag placeholder, matches one and only one tag
// //
// NOTE: "a" is equivalent to 'a'
// NOTE: paths have priority over sets: a/b:c -> a / b:c // NOTE: paths have priority over sets: a/b:c -> a / b:c
// NOTE: there is a special case pattern '*a*' that matches the same // NOTE: there is a special case pattern '*a*' that matches the same
// way as 'a', this is used in methods where 'a' is used as an // way as 'a', this is used in methods where 'a' is used as an
@ -509,7 +515,7 @@ var BaseTagsPrototype = {
b.filter(function(t){ b.filter(function(t){
return a == t }) return a == t })
: b != null ? : b != null ?
(a == b !!(a == b
|| (definitions || (definitions
&& (definitions[a] == b && (definitions[a] == b
|| a == definitions[b] || a == definitions[b]
@ -732,6 +738,9 @@ var BaseTagsPrototype = {
// (i.e. ['a:b:c', 'a:b', 'a:c', 'a']) and will only return // (i.e. ['a:b:c', 'a:b', 'a:c', 'a']) and will only return
// the actual full match and an individual tag match... // the actual full match and an individual tag match...
// XXX should it??? // XXX should it???
// XXX should we support partial quoting?
// ...e.g. a:"b" -- search for any set explicitly containing
// 'b' and any tag containing an 'a'...
search: function(query, tags){ search: function(query, tags){
var that = this var that = this
tags = tags == null ? tags = tags == null ?
@ -968,7 +977,7 @@ var BaseTagsPrototype = {
}, },
// NOTE: this supports tag patterns (see: .match(..)) // NOTE: this supports tag patterns (see: .match(..))
// NOTE: non-pattern tags are matched explicitly. // NOTE: non-pattern tags are matched explicitly.
// XXX BUG?: should this remove tags directly (current) or via matching?? // XXX Q: should this remove tags directly (current) or via matching??
// .tag('a:b', 'x') // .tag('a:b', 'x')
// .untag('a', 'x') -- this will do nothing. // .untag('a', 'x') -- this will do nothing.
// .untag('*a*', 'x') -- remove the tag. // .untag('*a*', 'x') -- remove the tag.
@ -978,6 +987,7 @@ var BaseTagsPrototype = {
// .untag('"a"', 'x') -- should remove tagged explicitly // .untag('"a"', 'x') -- should remove tagged explicitly
// with "a"... // with "a"...
// .untag('a', 'x') -- like the current '*a*' // .untag('a', 'x') -- like the current '*a*'
// what should the default be????
untag: function(tags, value){ untag: function(tags, value){
var that = this var that = this
var index = this.__index = this.__index || {} var index = this.__index = this.__index || {}
@ -1077,95 +1087,6 @@ var BaseTagsPrototype = {
(that.tag(tag, v), true) (that.tag(tag, v), true)
: null) }) }, : null) }) },
// Rename a tag...
//
// Rename tag...
// .rename(from, to)
// -> this
//
// Rename a tag in list of tags...
// .rename(from, to, tag, ...)
// .rename(from, to, [tag, ...])
// -> tags
//
// NOTE: if to is '' this will remove all occurrences of from.
// NOTE: if any renamed tag is renamed to '' it will be removed
// untagging all relevant values...
//
// XXX need to sanitize tag -- it can not contain regex characters...
// ...should we guard against this???
rename: function(tag, to, ...tags){
var that = this
// XXX should we bo more pedantic here???
tag = this.normalize(tag)
if(tag == ''){
throw new Error(`.rename(..): first argument can not be an empty string.`) }
if(/[:\\\/]/.test(tag)){
throw new Error(
`.rename(..): only support singular tag renaming, got: "${tag}"`) }
// XXX too strict???
if(!/^[a-z0-9]+$/.test(tag)){
throw new Error(
`.rename(..): first argument must be a valid single tag, got: "${tag}"`) }
to = this.normalize(to)
if(/[\\\/]/.test(to)){
throw new Error(
`.rename(..): only support tags and tag sets as renaming target, got: "${to}"`) }
tags = new Set(normalizeSplit(tags))
// prepare for the replacement...
var pattern = new RegExp(`(^|[:\\\\\\/])${tag}(?=$|[:\\\\\\/])`, 'g')
var target = `$1${to}`
var patchSet = function(s){
that.match(tag, [...s || []])
.forEach(function(tag){
s.delete(tag)
var t = that.normalize(tag.replace(pattern, target))
t != ''
&& s.add(t)
})
return s
}
var patchObj = function(o, patchValue){
that.match(tag, Object.keys(o || {}))
.forEach(function(m){
var value = o[m]
delete o[m]
var t = that.normalize(m.replace(pattern, target))
t != ''
&& (o[t] = value)
})
patchValue
&& Object.keys(o || {})
.forEach(function(m){
var v = o[m]
if(that.match(tag, v)){
var t = that.normalize(v.replace(pattern, target))
t == '' ?
(delete o[m])
: (o[m] = t)
}
})
return o
}
// rename tags in list...
if(arguments.length > 2){
return [...patchSet(tags)]
// rename actual data...
} else {
patchSet(this.persistent || [])
patchObj(this.__index || {})
patchObj(this.definitions || {}, true)
}
return this
},
// Replace tags... // Replace tags...
// //
// Replace tags... // Replace tags...
@ -1195,6 +1116,7 @@ var BaseTagsPrototype = {
// reachability or definitions... // reachability or definitions...
// NOTE: this will match tags in .__index, .persistent and .definitions // NOTE: this will match tags in .__index, .persistent and .definitions
// //
// XXX revise...
replace: function(tag, to, ...tags){ replace: function(tag, to, ...tags){
var that = this var that = this
tags = normalizeSplit(tags) tags = normalizeSplit(tags)
@ -1270,6 +1192,54 @@ var BaseTagsPrototype = {
res.flat() res.flat()
: this : this
}, },
// Rename a tag...
//
// Rename tag...
// .rename(from, to)
// -> this
//
// Rename a tag in list of tags...
// .rename(from, to, tag, ...)
// .rename(from, to, [tag, ...])
// -> tags
//
// NOTE: if to is '' this will remove all occurrences of from.
// NOTE: if any renamed tag is renamed to '' it will be removed
// untagging all relevant values...
//
// XXX need to sanitize tag -- it can not contain regex characters...
// ...should we guard against this???
rename: function(tag, to, ...tags){
var that = this
// XXX should we be more pedantic here???
tag = this.normalize(tag)
if(tag == ''){
throw new Error(`.rename(..): first argument can not be an empty string.`) }
if(/[:\\\/]/.test(tag)){
throw new Error(
`.rename(..): only support singular tag renaming, got: "${tag}"`) }
// XXX too strict???
if(!/^[a-z0-9]+$/.test(tag)){
throw new Error(
`.rename(..): first argument must be a valid single tag, got: "${tag}"`) }
to = this.normalize(to)
if(/[\\\/]/.test(to)){
throw new Error(
`.rename(..): only support tags and tag sets as renaming target, got: "${to}"`) }
// prepare for the replacement...
var pattern = new RegExp(
`(^|[${this.SET_SEPARATOR}\\${this.PATH_SEPARATOR}])`
+`${tag}`
+`(?=$|[${this.SET_SEPARATOR}\\${this.PATH_SEPARATOR}])`, 'g')
var target = `$1${to}`
return this.replace(tag,
function(from){
return from.replace(pattern, target) }, ...tags)
},
// NOTE: this is a short hand to .rename(tag, '', ..) for extra // NOTE: this is a short hand to .rename(tag, '', ..) for extra
// docs see that... // docs see that...
removeTag: function(tag, ...tags){ removeTag: function(tag, ...tags){
@ -2010,17 +1980,6 @@ var TagsWithHandlersPrototype = {
return object.parent(TagsWithHandlersPrototype.untag, this).call(this, return object.parent(TagsWithHandlersPrototype.untag, this).call(this,
that.handleSpecialTag(tags, 'untag', value), that.handleSpecialTag(tags, 'untag', value),
...[...arguments].slice(1)) }, ...[...arguments].slice(1)) },
// handler: action(tag, 'remove')
// handler: action(to, 'rename', tag)
rename: function(tag, to, ...tags){
return object.parent(TagsWithHandlersPrototype.rename, this).call(this,
tag,
arguments.length == 2 ?
(to == '' ?
this.handleSpecialTag(tag, 'remove')
: this.handleSpecialTag(to, 'rename', tag))
: to,
...[...arguments].slice(2)) },
// handler: action(tag, 'replace', from) // handler: action(tag, 'replace', from)
replace: function(tag, to, ...tags){ replace: function(tag, to, ...tags){
// XXX can we avoid doing this here??? // XXX can we avoid doing this here???
@ -2030,7 +1989,7 @@ var TagsWithHandlersPrototype = {
} }
return object.parent(TagsWithHandlersPrototype.replace, this).call(this, return object.parent(TagsWithHandlersPrototype.replace, this).call(this,
tag, tag,
arguments.length == 2 ? arguments.length <= 2 ?
(to instanceof Function ? (to instanceof Function ?
// wrap the handler... // wrap the handler...
function(tag){ function(tag){
@ -2112,11 +2071,17 @@ var TagsWithDictPrototype = {
// //
normalizeSave: function(...tags){ normalizeSave: function(...tags){
var dict = this.dict = this.dict || {} var dict = this.dict = this.dict || {}
var res = this.normalize(this.splitTag(...tags)) var res = this.normalize(...tags)
// unaltered tags...
tags = this.splitTag(normalizeSplit(tags)) tags = this.splitTag(normalizeSplit(tags))
// NOTE: we first split then normalize (order significant) because
// we need to conserve the order of the individual tags
// consistent with tags above...
// XXX can we avoid normalizing twice???
var names = this.normalize(this.splitTag(...tags))
;(res instanceof Array ? res : [res]) ;(names instanceof Array ? names : [names])
.forEach(function(tag, i){ .forEach(function(tag, i){
tag = tag.trim() tag = tag.trim()
var value = tags[i].trim() var value = tags[i].trim()
@ -2218,15 +2183,6 @@ var TagsWithDictPrototype = {
this.removeOrphansFromDict(tags) this.removeOrphansFromDict(tags)
return res return res
}, },
rename: function(from, to, ...tags){
var res = object.parent(TagsWithDictPrototype.rename, this).call(this, ...arguments)
arguments.length == 2
&& this.normalizeSave(to)
this.removeOrphansFromDict(from)
return res
},
replace: function(tag, to, ...tags){ replace: function(tag, to, ...tags){
// XXX can we avoid doing this here??? // XXX can we avoid doing this here???
if(tag instanceof Function){ if(tag instanceof Function){