|
1 | | -const deepdiff = require('deep-diff'); |
2 | | -const format = require('date-fns/format'); |
3 | | -const humanizeStr = require('humanize-string'); |
4 | | -const titleize = require('titleize'); |
5 | | -const processArray = require('./src/process-array'); |
6 | | - |
7 | | -/** |
8 | | - * For non-array values |
9 | | - * (array values are special and need |
10 | | - * different handling to detect correctly.) |
11 | | - * |
12 | | - * 1) property name string |
13 | | - * which is the first non array-index path string eg... 'name' |
14 | | - * if it is an array it will say "Array 'name'" |
15 | | - * |
16 | | - * 2) property value string |
17 | | - * is written as "with a value of 'value'" |
18 | | - * |
19 | | - * 3) list path (optional) |
20 | | - * path in dot notation. eg...'(at Obj,a.b[1].c)' where dots |
21 | | - * indicate object addresses and brackets indicate array indices |
22 | | - * |
23 | | - * 4) verb |
24 | | - * the verb tells what type of change occurred |
25 | | - * enum [changed, added, removed] |
26 | | - * can select past tense or present tense |
27 | | - * ie...'was changed' or 'will be changed' |
28 | | - * for added and removed plain values this will end the string |
29 | | - * |
30 | | - * 4b) If the verb was changed - we need to show the change |
31 | | - * ie... was changed to x |
32 | | - */ |
33 | | -const saveForArrayPreProcessing = diff => |
34 | | - diff.kind === 'A' || typeof diff.path[diff.path.length - 1] === 'number'; |
35 | | - |
36 | | -function humanReadableDiff(lhs, rhs, config = {}) { |
37 | | - const objectName = config.objectName || 'Obj'; |
38 | | - const dontHumanizePropertyNames = config.dontHumanizePropertyNames || false; |
39 | | - const tense = config.tense || 'past'; |
40 | | - const techTerms = config.techTerms !== false; |
41 | | - const dateFormat = config.dateFormat || 'MM/dd/yyyy hh:mm a'; |
42 | | - const hidePath = config.hidePath ? techTerms : false; |
43 | | - const ignoreArrays = config.ignoreArrays || false; |
44 | | - const arrayMem = []; |
45 | | - |
46 | | - const terms = { |
47 | | - array: techTerms ? 'Array' : 'List', |
48 | | - index: techTerms ? 'index' : 'position' |
49 | | - }; |
50 | | - |
51 | | - let prefilter; |
52 | | - if (Array.isArray(config.prefilter)) |
53 | | - prefilter = (path, key) => |
54 | | - path.length === 0 && config.prefilter.includes(key); |
55 | | - else if (typeof config.prefilter === 'function') prefilter = config.prefilter; |
56 | | - |
57 | | - function humanize(prop) { |
58 | | - return dontHumanizePropertyNames ? prop : titleize(humanizeStr(prop)); |
59 | | - } |
60 | | - |
61 | | - function getPropertyString(diff) { |
62 | | - let propertyIndex = diff.path.length - 1; |
63 | | - while (typeof diff.path[propertyIndex] !== 'string') propertyIndex -= 1; |
64 | | - const property = diff.path[propertyIndex]; |
65 | | - if (diff.dotPath) return `${terms.array} "${humanize(property)}"`; |
66 | | - return `"${humanize(property)}"`; |
67 | | - } |
68 | | - |
69 | | - function formatPropertyValue(val) { |
70 | | - if (typeof val === 'string') return `"${val}"`; |
71 | | - if (typeof val === 'number' || typeof val === 'boolean') |
72 | | - return `"${String(val)}"`; |
73 | | - if (val instanceof Date) return `"${format(val, dateFormat)}"`; |
74 | | - return JSON.stringify(val); |
75 | | - } |
76 | | - |
77 | | - function getPropertyValueString(diff) { |
78 | | - let formatted = ''; |
79 | | - if (diff.kind === 'N') formatted = formatPropertyValue(diff.rhs); |
80 | | - if (diff.kind === 'D' || diff.kind === 'E') |
81 | | - formatted = formatPropertyValue(diff.lhs); |
82 | | - if (diff.val) { |
83 | | - formatted = formatPropertyValue(diff.val); |
84 | | - return ` had a value of ${formatted}`; |
85 | | - } |
86 | | - |
87 | | - return ` with a value of ${formatted}`; |
88 | | - } |
89 | | - |
90 | | - function getPathString(diff) { |
91 | | - if (hidePath) return ''; |
92 | | - |
93 | | - if (diff.dotPath) { |
94 | | - diff.path = diff.dotPath.split('.'); |
95 | | - return `(at ${objectName}.${diff.dotPath})`; |
96 | | - } |
97 | | - |
98 | | - const path = diff.path.reduce( |
99 | | - (acc, val, i) => |
100 | | - typeof val === 'string' |
101 | | - ? typeof diff.path[i + 1] === 'string' |
102 | | - ? acc.concat(`${String(val)}.`) |
103 | | - : acc.concat(String(val)) |
104 | | - : typeof diff.path[i + 1] === 'string' |
105 | | - ? acc.concat(`[${String(val)}].`) |
106 | | - : acc.concat(`[${String(val)}]`), |
107 | | - '' |
108 | | - ); |
109 | | - return `(at ${objectName}.${path})`; |
110 | | - } |
111 | | - |
112 | | - function getVerbString(diff) { |
113 | | - const verb = { |
114 | | - N: 'added', |
115 | | - D: 'removed', |
116 | | - E: 'changed', |
117 | | - I: 'inserted', |
118 | | - R: 'removed' |
119 | | - }[diff.kind]; |
120 | | - |
121 | | - const preVerb = tense === 'past' ? 'was' : 'will be'; |
122 | | - |
123 | | - if (['I', 'R'].includes(diff.kind)) return `${verb}`; |
124 | | - |
125 | | - return `${preVerb} ${verb}${ |
126 | | - verb === 'changed' ? ` to ${formatPropertyValue(diff.rhs)}` : '' |
127 | | - }`; |
128 | | - } |
129 | | - |
130 | | - function reducer(acc, diff) { |
131 | | - // don't process array diffs |
132 | | - // until they have been pre-processed |
133 | | - if (!ignoreArrays && saveForArrayPreProcessing(diff)) { |
134 | | - arrayMem.push(diff); |
135 | | - return acc; |
136 | | - } |
137 | | - |
138 | | - const property = getPropertyString(diff); |
139 | | - const propertyValue = getPropertyValueString(diff); |
140 | | - const path = getPathString(diff); |
141 | | - const verb = getVerbString(diff); |
142 | | - |
143 | | - let diffString = ''; |
144 | | - |
145 | | - if (diff.dotPath) { |
146 | | - diffString = `${property} ${path},${propertyValue} ${verb} at ${ |
147 | | - terms.index |
148 | | - } ${techTerms ? diff.index : diff.index + 1}`; |
149 | | - } else if (path) |
150 | | - diffString = `${property},${propertyValue} ${path} ${verb}`; |
151 | | - else diffString = `${property},${propertyValue} ${verb}`; |
152 | | - |
153 | | - return acc.concat(diffString); |
154 | | - } |
155 | | - |
156 | | - function humanReadable(lhs, rhs) { |
157 | | - const differences = deepdiff(lhs, rhs, prefilter); |
158 | | - if (!differences) return []; |
159 | | - const changes = differences.reduce(reducer, []); |
160 | | - const arrDiffs = processArray(arrayMem, lhs, rhs); |
161 | | - const changeStrings = changes.concat(arrDiffs.reduce(reducer, [])); |
162 | | - return changeStrings; |
163 | | - } |
164 | | - |
165 | | - return humanReadable(lhs, rhs); |
166 | | -} |
167 | | - |
168 | | -module.exports = humanReadableDiff; |
| 1 | +module.exports = require('./src/diff'); |
0 commit comments