Skip to content

Commit 4f7719e

Browse files
committed
fix: handle duplicate keys
1 parent e197ac1 commit 4f7719e

File tree

2 files changed

+56
-43
lines changed

2 files changed

+56
-43
lines changed

src/properties.spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,21 @@ describe('data access', () => {
147147

148148
expect(() => properties.get(config, 'foo')).toThrowError()
149149
})
150+
151+
it.each([
152+
['foo=bar', 'bar'],
153+
['foo bar', 'bar'],
154+
['foo : bar', 'bar'],
155+
['foo := bar', '= bar'],
156+
['foo::bar', ':bar']
157+
])('should handle separator "%s"', (line: string, value: string) => {
158+
const config: properties.Properties = {
159+
lines: [line]
160+
}
161+
162+
const result = properties.get(config, 'foo')
163+
expect(result).toBe(value)
164+
})
150165
})
151166

152167
describe('set value', () => {

src/properties.ts

+41-43
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,16 @@ export function* list(config: Properties): Generator<KeyValuePair> {
9393
* @return Found value, or undefined. Value is properly unescaped.
9494
*/
9595
export const get = (config: Properties, key: string): string | undefined => {
96-
// Find existing
97-
const {value} = findValue(config.lines, key)
96+
let value: string | undefined = undefined
97+
98+
// Find last value
99+
for (const entry of listPairs(config.lines)) {
100+
// If found, remember it
101+
if (key === entry.key) {
102+
value = entry.value
103+
}
104+
}
105+
98106
return value
99107
}
100108

@@ -132,6 +140,9 @@ export const toMap = (config: Properties): Map<string, string> => {
132140
return result
133141
}
134142

143+
const formatLine = (key: string, value: string, sep: string) =>
144+
`${escapeKey(key)}${sep}${escapeValue(value)}`
145+
135146
/**
136147
* Set or remove value for the given key.
137148
*
@@ -146,22 +157,29 @@ export const set = (
146157
value: string | undefined | null,
147158
options?: {separator?: string}
148159
): void => {
149-
// Find existing
150-
const {start, len, sep} = findValue(config.lines, key)
151-
152-
// Prepare value
153-
const items =
154-
typeof value === 'string'
155-
? [`${escapeKey(key)}${options?.separator || sep || '='}${escapeValue(value)}`]
156-
: []
157-
158-
// If found
159-
if (start >= 0 && len > 0) {
160-
// Replace
161-
config.lines.splice(start, len, ...items)
162-
} else {
163-
// Not found, append
164-
config.lines.push(...items)
160+
let sep = '='
161+
let found = false
162+
163+
// Find all entries
164+
for (const entry of listPairs(config.lines)) {
165+
// Remember separator
166+
if (entry.sep) sep = entry.sep
167+
168+
// If found, either replace or remove
169+
if (key === entry.key) {
170+
const items =
171+
!found && typeof value === 'string'
172+
? [formatLine(key, value, options?.separator || sep)]
173+
: []
174+
175+
config.lines.splice(entry.start, entry.len, ...items)
176+
found = true
177+
}
178+
}
179+
180+
// Not found, append
181+
if (!found && typeof value === 'string') {
182+
config.lines.push(formatLine(key, value, options?.separator || sep))
165183
}
166184
}
167185

@@ -177,29 +195,10 @@ export const remove = (config: Properties, key: string): void =>
177195
set(config, key, undefined)
178196

179197
/**
180-
* Find value indices.
198+
* Character iterator over lines of chars.
181199
*
182-
* @param lines Lines array.
183-
* @param key Key to be found.
200+
* @param lines Lines to iterate over.
184201
*/
185-
const findValue = (
186-
lines: string[],
187-
key: string
188-
): {start: number; len: number; sep: string; value?: string} => {
189-
let sep = '='
190-
for (const entry of listPairs(lines)) {
191-
// Remember separator
192-
if (entry.sep) sep = entry.sep
193-
// Return found value
194-
if (key === entry.key) {
195-
return entry
196-
}
197-
}
198-
199-
// Not found
200-
return {start: -1, len: 0, sep}
201-
}
202-
203202
function* chars(lines: string[]): Generator<{char: string, line: number}> {
204203
for (let i = 0; i < lines.length; i++) {
205204
const line = lines[i]
@@ -337,7 +336,7 @@ function* listPairs(lines: string[]): Generator<{
337336
state.unicode = '0x'
338337
} else {
339338
// Special char
340-
state.key += unescapeChar(char)
339+
state.key += unescapeControlChar(char)
341340
}
342341
} else {
343342
// Normal char
@@ -415,7 +414,7 @@ function* listPairs(lines: string[]): Generator<{
415414
state.unicode = '0x'
416415
} else {
417416
// Special char
418-
state.value += unescapeChar(char)
417+
state.value += unescapeControlChar(char)
419418
}
420419
} else {
421420
// Normal char
@@ -427,8 +426,7 @@ function* listPairs(lines: string[]): Generator<{
427426
}
428427
}
429428

430-
// Very simple implementation
431-
const unescapeChar = (c: string): string => {
429+
const unescapeControlChar = (c: string): string => {
432430
switch (c) {
433431
case 'r':
434432
return '\r'

0 commit comments

Comments
 (0)