-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
119 lines (102 loc) · 3.86 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"use strict";
const { curry, over, lensProp, pipe, sortBy, prop, map, groupWith, equals, head } = require("ramda");
const transform = require("@bluealba/object-transform");
const calculateDiff = require("deep-diff");
const chalk = require("chalk");
const { lensPath, view } = require("ramda");
const reconciliate = curry((rules, before, after) => {
const beforeNormalized = normalizeBefore(rules)(before);
const afterNormalized = normalizeAfter(rules)(after);
const result = calculateDiff(beforeNormalized, afterNormalized) || [];
const diff = pipe(
map(over(lensProp("path"), path => path.join("."))),
sortBy(prop("path"))
)(result);
return new DiffResult(diff, before, after);
})
class DiffResult {
constructor(diff, before, after, displayPathReplacers = []) {
this.diff = diff;
this.before = before;
this.after = after;
this.displayPathReplacers = displayPathReplacers;
}
parseExpression(expression) {
//TODO: WIP, not sure about the notation. It only needs to replace captured groups!
return expression
.replace(/\./g, "\\.") //dots are textual dots
.replace(/\[\]/g, "\\d+") //square brackets match any index in an array, this won't work!
.replace(/\?/g, "[\\w\\d]+") //quotation mark matches any key
}
getValue(path) {
const splitPath = path.split(".").map(x => x.match(/^\d+$/) ? parseInt(x) : x);
return view(lensPath(splitPath))(this.before);
}
/**
* Consolidate all the differences that match a given expression into a single occurrence
*/
consolidate(expression) {
const regexp = new RegExp(`^${this.parseExpression(expression)}`);
const consolidatedDiff = pipe(
map(each => Object.assign({}, each, { groupPath: each.path.replace(regexp, expression) })),
sortBy(each => `${each.groupPath} ${JSON.stringify(each.rhs)} ${JSON.stringify(each.lhs)}`),
groupWith((each1, each2) => {
return equals(each1.groupPath, each2.groupPath)
&& equals(each1.lhs, each2.lhs)
&& equals(each1.rhs, each2.rhs)
}),
map(group => ({
path: group.length > 1 ? head(group).groupPath : head(group).path,
lhs: head(group).lhs,
rhs: head(group).rhs,
times: group
}))
)(this.diff)
return new DiffResult(consolidatedDiff, this.before, this.after, this.displayPathReplacers)
}
displayKey(expression, keyExtractor) {
const regexp = new RegExp(`^(${this.parseExpression(expression)})\\.([\\d\\w]+)(.*)$`);
const displayPathReplacer = path => {
if (path.match(regexp)) {
const keyHolderPath = path.replace(regexp, `$1.$2`);
const keyHolder = this.getValue(keyHolderPath);
const extractedKey = keyExtractor(keyHolder);
return path.replace(regexp, `$1.${extractedKey}$3`)
} else {
return path;
}
}
return new DiffResult(this.diff, this.before, this.after, [...this.displayPathReplacers, displayPathReplacer])
}
printDiff() {
return pipe(
map(each => {
let path = this.displayPathReplacers.reduce((path, replacer) => replacer(path), each.path);
let line = `${chalk.yellow(path)} -> ${chalk.grey(JSON.stringify(each.lhs))} ${chalk.white(JSON.stringify(each.rhs))}`
if (each.times && each.times.length > 1) {
line = `${line} ${chalk.yellow(`(${each.times.length} times)`)}`
}
return line;
})
)(this.diff).join("\n");
}
}
function normalizeBefore(rules) {
const transformations = [
...(rules.remove || []).map(path => ({ type: "exclude", path })),
...(rules.ignore || []).map(path => ({ type: "exclude", path })),
...(rules.rename || []).map(([path, toPath]) => ({ type: "rename", path, toPath })),
...(rules.map || []).map(([path, fn]) => ({ type: "map", path, fn }))
];
return transform(transformations);
}
function normalizeAfter(rules) {
const transformations = [
...(rules.add || []).map(path => ({ type: "exclude", path })),
...(rules.ignore || []).map(path => ({ type: "exclude", path }))
];
return transform(transformations);
}
module.exports = {
reconciliate
}