-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
patch.js
95 lines (94 loc) · 2.94 KB
/
patch.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
class Patch {
constructor(patchInput = "", rowDelimiter = "&", columnDelimiter = "=") {
this.rowDelimiter = rowDelimiter
this.columnDelimiter = columnDelimiter
// The pipeline of encodings. Operations will be run in order for encoding (and reveresed for decoding).
this.encoders = [
{
encode: (str) => encodeURIComponent(str),
decode: (str) => decodeURIComponent(str),
},
{
encode: (str) =>
this.replaceAll(
str,
columnDelimiter,
encodeURIComponent(columnDelimiter)
),
decode: (str) =>
this.replaceAll(
str,
encodeURIComponent(columnDelimiter),
columnDelimiter
),
},
{
encode: (str) =>
this.replaceAll(str, rowDelimiter, encodeURIComponent(rowDelimiter)),
decode: (str) =>
this.replaceAll(str, encodeURIComponent(rowDelimiter), rowDelimiter),
},
{
// Turn "%20" into "+" for prettier urls.
encode: (str) => str.replace(/\%20/g, "+"),
decode: (str) => str.replace(/\+/g, "%20"),
},
]
if (typeof patchInput === "string")
this.uriEncodedString = patchInput.replace(/^\#/, "")
else if (Array.isArray(patchInput))
this.uriEncodedString = this.arrayToEncodedString(patchInput)
else this.uriEncodedString = this.objectToEncodedString(patchInput)
}
replaceAll(str, search, replace) {
return str.split(search).join(replace)
}
objectToEncodedString(obj) {
return Object.keys(obj)
.map((identifierCell) => {
const value = obj[identifierCell]
const valueCells = value instanceof Array ? value : [value]
const row = [identifierCell, ...valueCells].map((cell) =>
this.encodeCell(cell)
)
return row.join(this.columnDelimiter)
})
.join(this.rowDelimiter)
}
arrayToEncodedString(arr) {
return arr
.map((line) =>
line.map((cell) => this.encodeCell(cell)).join(this.columnDelimiter)
)
.join(this.rowDelimiter)
}
get array() {
return this.uriEncodedString
.split(this.rowDelimiter)
.map((line) =>
line.split(this.columnDelimiter).map((cell) => this.decodeCell(cell))
)
}
get object() {
const patchObj = {}
if (!this.uriEncodedString) return patchObj
this.array.forEach((cells) => {
const identifierCell = cells.shift()
patchObj[identifierCell] = cells.length > 1 ? cells : cells[0] // If a single value, collapse to a simple tuple. todo: sure about this design?
})
return patchObj
}
encodeCell(unencodedCell) {
return this.encoders.reduce(
(str, encoder) => encoder.encode(str),
unencodedCell
)
}
decodeCell(encodedCell) {
return this.encoders
.slice()
.reverse()
.reduce((str, encoder) => encoder.decode(str), encodedCell)
}
}
if (typeof module !== "undefined") module.exports = { Patch }