mirror of
https://github.com/flynx/diff.js.git
synced 2025-10-28 10:30:09 +00:00
reworked .filter(..) path syntax + notes + docs...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
13492f9907
commit
a6149be9b4
22
README.md
22
README.md
@ -262,9 +262,31 @@ Apply "diff* (or *patch*) `X` to `X'` state.
|
||||
`diff.unpatch(X') -> X`
|
||||
Undo *diff" application to `X'` returning it to `X` state.
|
||||
|
||||
This is equivalent to: `diff.reverse().patch(X')`
|
||||
|
||||
`diff.check(X) -> bool`
|
||||
Check if *diff* is compatible/applicable to `X`. This essentially checks if the *left hand side* of the *diff* matches the corresponding nodes of `X`.
|
||||
|
||||
`diff.reverse() -> diff`
|
||||
Generate a new *child diff* where `A` and `B` are reversed.
|
||||
|
||||
`diff.filter(path | filter) -> diff`
|
||||
Generate a new *child diff* leaving only changes that match the `path`/`filter`
|
||||
|
||||
The `path` is a `"/"` or `"\"` separated string that supports the following item syntax:
|
||||
- `"*"` - matches any item (same as: `ANY`).
|
||||
- `"**"` - matches 0 or more items.
|
||||
- `"a|b"` - matches either `a` or `b` (same as: `OR('a', 'b')`)
|
||||
- `"!a"` - matches anything but `a` (smae as: `NOT('a')`)
|
||||
|
||||
*Note that `"**"` is a special case in that it can not be combined with other patterns above (e.g. in `"a|**"` the string `"**"` is treated literally and has not special meaning).*
|
||||
|
||||
`diff.merge(diff) -> diff`
|
||||
XXX
|
||||
|
||||
`diff.end() -> diff`
|
||||
Return the *parent diff* that was used to generate the current *child diff* or the current diff if there is not parent.
|
||||
|
||||
`diff.json() -> JSON`
|
||||
Serialize the *diff* to JSON. Note that the output may or may not be JSON compatible depending on the inputs.
|
||||
|
||||
|
||||
216
diff.js
216
diff.js
@ -969,6 +969,122 @@ module.Types = {
|
||||
})
|
||||
},
|
||||
|
||||
// Filter diff changes and return a new diff...
|
||||
//
|
||||
// .filter(path)
|
||||
// .filter(func)
|
||||
// -> diff
|
||||
//
|
||||
// path can be either a '/' separated string of path elements or
|
||||
// an array...
|
||||
//
|
||||
// path if given as a string supports the following syntax:
|
||||
// * - matches any single path element (like ANY)
|
||||
// a|b - matches either a or b
|
||||
// !a - matches anything but a
|
||||
// XXX do we need grouping and quoting???
|
||||
//
|
||||
// Special case:
|
||||
// ** - matches 0 or more path elements
|
||||
// NOTE: '**' can't be used with other patterns from
|
||||
// the above.
|
||||
//
|
||||
// NOTE: array path also supports patterns...
|
||||
filter: function(diff, filter){
|
||||
// string filter...
|
||||
filter = typeof(filter) == typeof('str') ?
|
||||
filter
|
||||
.trim()
|
||||
// remove leading and trailing '/' or '\'
|
||||
.replace(/(^[\\\/]+|[\\\/]+$)/g, '')
|
||||
.split(/[\\\/]+/)
|
||||
// 'a|b' -> OR('a', 'b')
|
||||
// '*' -> ANY
|
||||
// '!a' -> NOT('a')
|
||||
// NOTE: '**' is handled differently and later...
|
||||
.map(function(e){
|
||||
e = e
|
||||
.split(/\|/)
|
||||
.map(function(e){
|
||||
return e == '*' ? ANY
|
||||
: e[0] == '!' ? NOT(e.slice(1))
|
||||
: e })
|
||||
return e.length == 1 ? e[0] : OR(...e)
|
||||
})
|
||||
: filter
|
||||
|
||||
// path filter (non-function)...
|
||||
if(!(filter instanceof Function)){
|
||||
// normalize path...
|
||||
// format:
|
||||
// [
|
||||
// '**' | [ .. ],
|
||||
// ...
|
||||
// ]
|
||||
// XXX when OF(..) is ready, replace '**' with OF(ANY, ANY)...
|
||||
var pattern = (filter instanceof Array ? filter : [filter])
|
||||
// remove consecutive repeating '**'
|
||||
.filter(function(e, i, lst){
|
||||
return e == '**' && lst[i-1] != '**' || true })
|
||||
// split to array sections at '**'...
|
||||
.reduce(function(res, e){
|
||||
var n = res.length-1
|
||||
e == '**' ?
|
||||
res.push('**')
|
||||
: (res.length == 0 || res[n] == '**') ?
|
||||
res.push([e])
|
||||
: res[n].push(e)
|
||||
return res
|
||||
}, [])
|
||||
|
||||
// min length...
|
||||
var min = pattern
|
||||
.reduce(function(l, e){
|
||||
return l + (e instanceof Array ? e.length : 0) }, 0)
|
||||
|
||||
// XXX account for pattern/path end...
|
||||
var test = function(path, pattern){
|
||||
return (
|
||||
// end of path/pattern...
|
||||
path.length == 0 && pattern.length == 0 ?
|
||||
true
|
||||
|
||||
// consumed pattern with path left over -> fail...
|
||||
: (path.length > 0 && pattern.length == 0)
|
||||
|| (path.length == 0 && pattern.length > 1)?
|
||||
false
|
||||
|
||||
// '**' -> test, skip elem and repeat...
|
||||
: pattern[0] == '**' ?
|
||||
(test(path, pattern.slice(1))
|
||||
|| test(path.slice(1), pattern))
|
||||
|
||||
// compare sections...
|
||||
: (cmp(
|
||||
path.slice(0, pattern[0].length),
|
||||
pattern[0])
|
||||
// test next section...
|
||||
&& test(
|
||||
path.slice(pattern[0].length),
|
||||
pattern.slice(1)))) }
|
||||
|
||||
// XXX Q: should we ignore the last element of the path???
|
||||
filter = function(change, i, lst){
|
||||
return test(change.path, pattern) }
|
||||
}
|
||||
|
||||
return diff.filter(filter.bind(this))
|
||||
},
|
||||
|
||||
// XXX there are two approaches to this:
|
||||
// 1) naive: simply concatenate all the changes in order...
|
||||
// 2) filter and merge changes based on path...
|
||||
// XXX do we need a conflict resolution policy???
|
||||
merge: function(diff, other){
|
||||
// XXX
|
||||
return this.flatten(diff).concat(this.flatten(other))
|
||||
},
|
||||
|
||||
|
||||
// User API...
|
||||
|
||||
@ -1260,95 +1376,6 @@ module.Types = {
|
||||
: !that.cmp(change.A, target[key])
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
// Filter diff changes and return a new diff...
|
||||
//
|
||||
// .filter(path)
|
||||
// .filter(func)
|
||||
// -> diff
|
||||
//
|
||||
// path can be either a '/' separated string of path elements or
|
||||
// an array...
|
||||
//
|
||||
// path supports a limited glob syntax:
|
||||
// * - matches any single path element (like ANY)
|
||||
// ** - matches 0 or more path elements
|
||||
//
|
||||
// NOTE: array path also supports patterns...
|
||||
//
|
||||
filter: function(diff, filter){
|
||||
// string filter...
|
||||
filter = typeof(filter) == typeof('str') ?
|
||||
filter.split(/[\\\/]/)
|
||||
: filter
|
||||
|
||||
// path filter (non-function)...
|
||||
if(!(filter instanceof Function)){
|
||||
// normalize path...
|
||||
// format:
|
||||
// [
|
||||
// '**' | [ .. ],
|
||||
// ...
|
||||
// ]
|
||||
// XXX when OF(..) is ready, replace '**' with OF(ANY, ANY)...
|
||||
var pattern = (filter instanceof Array ? filter : [filter])
|
||||
// '*' -> ANY
|
||||
.map(function(e){
|
||||
return e == '*' ? ANY : e })
|
||||
// remove consecutive repeating '**'
|
||||
.filter(function(e, i, lst){
|
||||
return e == '**' && lst[i-1] != '**' || true })
|
||||
// split to array sections at '**'...
|
||||
.reduce(function(res, e){
|
||||
var n = res.length-1
|
||||
e == '**' ?
|
||||
res.push('**')
|
||||
: (res.length == 0 || res[n] == '**') ?
|
||||
res.push([e])
|
||||
: res[n].push(e)
|
||||
return res
|
||||
}, [])
|
||||
|
||||
// min length...
|
||||
var min = pattern
|
||||
.reduce(function(l, e){
|
||||
return l + (e instanceof Array ? e.length : 0) }, 0)
|
||||
|
||||
// XXX account for pattern/path end...
|
||||
var test = function(path, pattern){
|
||||
return (
|
||||
// end of path/pattern...
|
||||
path.length == 0 && pattern.length == 0 ?
|
||||
true
|
||||
|
||||
// consumed pattern with path left over -> fail...
|
||||
: (path.length > 0 && pattern.length == 0)
|
||||
|| (path.length == 0 && pattern.length > 1)?
|
||||
false
|
||||
|
||||
// '**' -> test, skip elem and repeat...
|
||||
: pattern[0] == '**' ?
|
||||
(test(path, pattern.slice(1))
|
||||
|| test(path.slice(1), pattern))
|
||||
|
||||
// compare sections...
|
||||
: (cmp(
|
||||
path.slice(0, pattern[0].length),
|
||||
pattern[0])
|
||||
// test next section...
|
||||
&& test(
|
||||
path.slice(pattern[0].length),
|
||||
pattern.slice(1)))) }
|
||||
|
||||
// XXX Q: should we ignore the last element of the path???
|
||||
filter = function(change, i, lst){
|
||||
return test(change.path, pattern) }
|
||||
}
|
||||
|
||||
return diff.filter(filter.bind(this))
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -2217,17 +2244,12 @@ var DiffPrototype = {
|
||||
res.parent = this
|
||||
return res
|
||||
},
|
||||
// XXX
|
||||
|
||||
// XXX should this set .parent ????
|
||||
merge: function(diff){
|
||||
var res = this.clone()
|
||||
|
||||
// XXX there are two approaches to this:
|
||||
// 1) naive: simply concatenate all the changes in order...
|
||||
// 2) filter and merge changes based on path...
|
||||
// XXX do we need a conflict resolution policy???
|
||||
|
||||
res.diff = this.constructor.types.merge.call(this, this.diff, diff.diff)
|
||||
res.parent = this
|
||||
|
||||
return res
|
||||
},
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user