properties-file 2.2.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -90
- package/lib/editor/index.d.ts +104 -0
- package/lib/editor/index.js +249 -0
- package/lib/index.d.ts +12 -6
- package/lib/index.js +14 -9
- package/lib/loader/webpack.js +2 -2
- package/lib/properties.d.ts +37 -5
- package/lib/properties.js +139 -14
- package/lib/property.d.ts +39 -23
- package/lib/property.js +55 -46
- package/package.json +13 -11
- package/lib/content/index.d.ts +0 -18
- package/lib/content/index.js +0 -59
- package/lib/file/index.d.ts +0 -21
- package/lib/file/index.js +0 -30
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|

|
|
7
7
|
[](https://snyk.io/test/github/Avansai/properties-file?targetFile=package.json)
|
|
8
8
|
|
|
9
|
-
`.properties` file
|
|
9
|
+
`.properties` file parser, editor, formatter and Webpack loader.
|
|
10
10
|
|
|
11
11
|
## Installation 💻
|
|
12
12
|
|
|
13
|
-
> ⚠
|
|
13
|
+
> ⚠ In April 2023, we released version 3 of this package, which includes breaking changes. Please refer to the [migration guide](./V2-TO-V3-MIGRATION-GUIDE.md) before upgrading.
|
|
14
14
|
|
|
15
15
|
Add the package as a dependency:
|
|
16
16
|
|
|
@@ -20,30 +20,31 @@ npm install properties-file
|
|
|
20
20
|
|
|
21
21
|
## What's in it for me? 🤔
|
|
22
22
|
|
|
23
|
-
- A modern TypeScript
|
|
23
|
+
- A modern library written entirely in TypeScript that exactly reproduces the [Properties Java implementation](/assets/java-implementation.md).
|
|
24
|
+
- Works for both Node.js applications and browsers that support at least [ES5](https://www.w3schools.com/js/js_es5.asp).
|
|
24
25
|
- Flexible APIs:
|
|
25
|
-
- `
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
- `escapeKey
|
|
29
|
-
-
|
|
26
|
+
- `getProperties` converts the content of `.properties` files to a key-value pair object.
|
|
27
|
+
- A `Properties` class provides insights into parsing data.
|
|
28
|
+
- A `PropertiesEditor` class enables the addition, edition, and removal of entries.
|
|
29
|
+
- `escapeKey` and `escapeValue` allow the conversion of any content to a `.properties` compatible format.
|
|
30
|
+
- The library also includes a Webpack loader to import `.properties` files directly into your application.
|
|
31
|
+
- Tiny ([under 2kB compressed](https://bundlephobia.com/package/properties-file)) with 0 dependencies.
|
|
30
32
|
- 100% test coverage based on the output from a Java implementation.
|
|
31
|
-
- Active maintenance (many popular
|
|
33
|
+
- Active maintenance (many popular `.properties` packages have been inactive for years).
|
|
32
34
|
|
|
33
35
|
## Usage 🎬
|
|
34
36
|
|
|
35
|
-
We put a lot of effort into
|
|
37
|
+
We have put a lot of effort into incorporating [TSDoc](https://tsdoc.org/) into all our APIs. If you are unsure about how to use certain APIs provided in our examples, please check directly in your IDE.
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
### `getProperties` (converting `.properties` to an object)
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
This API is probably the most used. You have a `.properties` file that you want to open and access like a simple key/value JSON object. Here is how this can be done with a single API call:
|
|
41
|
+
The most common use case for `.properties` files is for Node.js applications that need to read the file's content into a simple key-value pair object. Here is how this can be done with a single API call:
|
|
42
42
|
|
|
43
43
|
```ts
|
|
44
|
-
import {
|
|
44
|
+
import { readFileSync } from 'node:fs'
|
|
45
|
+
import { getProperties } from 'properties-file'
|
|
45
46
|
|
|
46
|
-
console.log(
|
|
47
|
+
console.log(getProperties(readFileSync('hello-world.properties')))
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
Output:
|
|
@@ -52,105 +53,134 @@ Output:
|
|
|
52
53
|
{ hello: 'hello', world: 'world' }
|
|
53
54
|
```
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
### `Properties` (using parsing metadata)
|
|
57
|
+
|
|
58
|
+
The `Properties` object is what makes `getProperties` work under the hood, but when using it directly, you can access granular parsing metadata. Here is an example of how the object can be used to find key collisions:
|
|
56
59
|
|
|
57
60
|
```ts
|
|
58
|
-
import {
|
|
61
|
+
import { Properties } from 'properties-file'
|
|
62
|
+
|
|
63
|
+
const properties = new Properties(
|
|
64
|
+
'hello = hello1\nworld = world1\nworld = world2\nhello = hello2\nworld = world3'
|
|
65
|
+
)
|
|
66
|
+
console.log(properties.format())
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Outputs:
|
|
70
|
+
*
|
|
71
|
+
* hello = hello1
|
|
72
|
+
* world = world1
|
|
73
|
+
* world = world2
|
|
74
|
+
* hello = hello2
|
|
75
|
+
* world = world3
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
properties.collection.forEach((property) => {
|
|
79
|
+
console.log(`${property.key} = ${property.value}`)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Outputs:
|
|
84
|
+
*
|
|
85
|
+
* hello = hello2
|
|
86
|
+
* world = world3
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
const keyCollisions = properties.getKeyCollisions()
|
|
59
90
|
|
|
60
|
-
|
|
91
|
+
keyCollisions.forEach((keyCollision) => {
|
|
92
|
+
console.warn(
|
|
93
|
+
`Found a key collision for key '${
|
|
94
|
+
keyCollision.key
|
|
95
|
+
}' on lines ${keyCollision.startingLineNumbers.join(
|
|
96
|
+
', '
|
|
97
|
+
)} (will use the value at line ${keyCollision.getApplicableLineNumber()}).`
|
|
98
|
+
)
|
|
99
|
+
})
|
|
61
100
|
|
|
62
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Outputs:
|
|
103
|
+
*
|
|
104
|
+
* Found a key collision for key 'hello' on lines 1, 4 (will use the value at line 4).
|
|
105
|
+
* Found a key collision for key 'world' on lines 2, 3, 5 (will use the value at line 5).
|
|
106
|
+
*/
|
|
63
107
|
```
|
|
64
108
|
|
|
65
|
-
|
|
109
|
+
For purposes where you require more parsing metadata, such as building a syntax highlighter, it is recommended that you access the `Property` objects included in the `Properties.collection`. These objects provide comprehensive information about each key-value pair.
|
|
66
110
|
|
|
67
|
-
|
|
111
|
+
### `PropertiesEditor` (editing `.properties` content)
|
|
68
112
|
|
|
69
|
-
|
|
113
|
+
In certain scenarios, it may be necessary to modify the content of the `.properties` key-value pair objects. This can be achieved easily using the `Properties` object, with the assistance of the `escapeKey` and `escapeValue` APIs, as demonstrated below:
|
|
70
114
|
|
|
71
115
|
```ts
|
|
72
|
-
import
|
|
73
|
-
import { EOL } from 'node:os'
|
|
74
|
-
import { getProperties } from 'properties-file'
|
|
116
|
+
import { Properties } from 'properties-file'
|
|
75
117
|
import { escapeKey, escapeValue } from 'properties-file/escape'
|
|
76
118
|
|
|
77
|
-
const properties =
|
|
119
|
+
const properties = new Properties('hello = hello\n# This is a comment\nworld = world')
|
|
78
120
|
const newProperties: string[] = []
|
|
79
|
-
console.dir(properties)
|
|
80
121
|
|
|
81
122
|
properties.collection.forEach((property) => {
|
|
82
|
-
const
|
|
83
|
-
|
|
123
|
+
const key = property.key === 'world' ? 'new world' : property.key
|
|
124
|
+
const value = property.value === 'world' ? 'new world' : property.value
|
|
125
|
+
newProperties.push(`${escapeKey(key)} = ${escapeValue(value)}`)
|
|
84
126
|
})
|
|
85
127
|
|
|
86
|
-
|
|
128
|
+
console.log(newProperties.join('\n'))
|
|
87
129
|
|
|
88
130
|
/**
|
|
89
131
|
* Outputs:
|
|
90
132
|
*
|
|
91
|
-
* hello
|
|
92
|
-
* world
|
|
93
|
-
*
|
|
133
|
+
* hello = hello
|
|
134
|
+
* new\ world = new world
|
|
94
135
|
*/
|
|
95
136
|
```
|
|
96
137
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Java's implementation of `Properties` is quite resilient. In fact, there are only two ways an exception can be thrown:
|
|
100
|
-
|
|
101
|
-
- The file is not found.
|
|
102
|
-
- A (`\u`) Unicode escape character is malformed.
|
|
103
|
-
|
|
104
|
-
This means that almost all files will be valid.
|
|
105
|
-
|
|
106
|
-
But what about a file that has duplicate keys? Duplicate keys have no reason to exist and they probably should have thrown errors as well but instead Java decided to simply overwrite the value with the latest occurrence in a file.
|
|
107
|
-
|
|
108
|
-
So how can we know if there were duplicate keys if we want to log some warnings? Simply by using `getProperties` which will return all the data that was used to parse the content. Here is an example on how it can be used:
|
|
109
|
-
|
|
110
|
-
```properties
|
|
111
|
-
# collisions-test.properties
|
|
112
|
-
hello: hello1
|
|
113
|
-
world: world1
|
|
114
|
-
world: world2
|
|
115
|
-
hello: hello2
|
|
116
|
-
world: world3
|
|
117
|
-
```
|
|
138
|
+
The limitation of this approach is that its output contains only valid keys, without any comments or whitespace. However, if you require a more advanced editor that preserves these original elements, then the `PropertiesEditor` object is exactly what you need.
|
|
118
139
|
|
|
119
140
|
```ts
|
|
120
|
-
import {
|
|
141
|
+
import { PropertiesEditor } from 'properties-file/editor'
|
|
121
142
|
|
|
122
|
-
const properties =
|
|
123
|
-
|
|
124
|
-
properties.collection.forEach((property) => {
|
|
125
|
-
console.log(`${property.key} => '${property.value}'`)
|
|
126
|
-
})
|
|
143
|
+
const properties = new PropertiesEditor('hello = hello\n# This is a comment\nworld = world')
|
|
144
|
+
console.log(properties.format())
|
|
127
145
|
|
|
128
146
|
/**
|
|
129
147
|
* Outputs:
|
|
130
148
|
*
|
|
131
|
-
* hello
|
|
132
|
-
*
|
|
133
|
-
*
|
|
149
|
+
* hello = hello
|
|
150
|
+
* # This is a comment
|
|
151
|
+
* world = world
|
|
134
152
|
*/
|
|
135
153
|
|
|
136
|
-
|
|
154
|
+
properties.insertComment('This is a multiline\ncomment before `newKey3`')
|
|
155
|
+
properties.insert('newKey3', 'This is my third key')
|
|
137
156
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
157
|
+
properties.insert('newKey1', 'This is my first new key', {
|
|
158
|
+
referenceKey: 'newKey3',
|
|
159
|
+
position: 'before',
|
|
160
|
+
comment: 'Below are the new keys being edited',
|
|
161
|
+
commentDelimiter: '!',
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
properties.insert('newKey2', 'こんにちは', {
|
|
165
|
+
referenceKey: 'newKey1',
|
|
166
|
+
position: 'after',
|
|
167
|
+
escapeUnicode: true,
|
|
146
168
|
})
|
|
147
169
|
|
|
170
|
+
properties.remove('hello')
|
|
171
|
+
console.log(properties.format())
|
|
172
|
+
|
|
148
173
|
/**
|
|
149
174
|
* Outputs:
|
|
150
175
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
176
|
+
* # This is a comment
|
|
177
|
+
* world = world
|
|
178
|
+
* ! Below are the new keys being edited
|
|
179
|
+
* newKey1 = This is my first new key
|
|
180
|
+
* newKey2 = \u3053\u3093\u306b\u3061\u306f
|
|
181
|
+
* # This is a multiline
|
|
182
|
+
* # comment before `newKey3`
|
|
183
|
+
* newKey3 = This is my third key
|
|
154
184
|
*/
|
|
155
185
|
```
|
|
156
186
|
|
|
@@ -202,17 +232,17 @@ Output:
|
|
|
202
232
|
|
|
203
233
|
## Why another `.properties` file package?
|
|
204
234
|
|
|
205
|
-
There are probably over 20 similar packages available but:
|
|
235
|
+
There are probably over 20 similar packages available, but:
|
|
206
236
|
|
|
207
|
-
-
|
|
208
|
-
-
|
|
237
|
+
- Many of the most popular packages have had no activity for over 5 years.
|
|
238
|
+
- Most packages will not replicate the current Java implementation.
|
|
209
239
|
- No package offers the same capabilities as this one.
|
|
210
240
|
|
|
211
|
-
Unfortunately the `.properties` file specification is not well
|
|
241
|
+
Unfortunately, the `.properties` file specification is not well-documented. One reason for this is that it was originally used in Java to store configurations. Today, most applications handle this using JSON, YAML, or other modern formats because these formats are more flexible.
|
|
212
242
|
|
|
213
243
|
### So why `.properties` files?
|
|
214
244
|
|
|
215
|
-
While many options
|
|
245
|
+
While many options exist today to handle configurations, `.properties` files remain one of the best options to store localizable strings (also known as messages). On the Java side, `PropertyResourceBundle` is how most implementations handle localization today. Because of its simplicity and maturity, `.properties` files remain one of the best options today when it comes to internationalization (i18n):
|
|
216
246
|
|
|
217
247
|
| File format | Key/value based | Supports inline comments | Built for localization | Good linguistic tools support |
|
|
218
248
|
| ------------- | ---------------- | ------------------------ | ---------------------- | ----------------------------- |
|
|
@@ -220,20 +250,19 @@ While many options exists today to handle configurations, `.properties` file rem
|
|
|
220
250
|
| `JSON` | No (can do more) | No (requires JSON5) | No | Depends on the schema |
|
|
221
251
|
| `YAML` | No (can do more) | Yes | No | Depends on the schema |
|
|
222
252
|
|
|
223
|
-
|
|
253
|
+
Having good JavaScript/TypeScript support for .properties files offers more internationalization (i18n) options.
|
|
224
254
|
|
|
225
255
|
### How does this package work?
|
|
226
256
|
|
|
227
|
-
Basically our goal was to offer parity with the Java implementation, which is the closest thing to a specification `.properties`
|
|
257
|
+
Basically, our goal was to offer parity with the Java implementation, which is the closest thing to a specification for `.properties` files. Here is the logic behind this package in a nutshell:
|
|
228
258
|
|
|
229
|
-
1.
|
|
230
|
-
2.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
6. Create a `PropertiesObject` that will include all `PropertyObjects` while removing collisions
|
|
259
|
+
1. The content is split by lines, creating an array of strings where each line is an element.
|
|
260
|
+
2. All lines are parsed to create a collection of `Property` objects that:
|
|
261
|
+
1. Identify key-value pair lines from the other lines (e.g., comments, blank lines, etc.).
|
|
262
|
+
2. Merge back multiline key-value pairs on single lines by removing trailing backslashes.
|
|
263
|
+
3. Unescape the keys and values.
|
|
235
264
|
|
|
236
|
-
Just like Java, if a Unicode
|
|
265
|
+
Just like Java, if a Unicode-escaped character (`\u`) is malformed, an error will be thrown. However, we do not recommend using Unicode-escaped characters, but rather using UTF-8 encoding that supports more characters.
|
|
237
266
|
|
|
238
267
|
## Additional references
|
|
239
268
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Properties } from '../properties';
|
|
2
|
+
/** The default separator between keys and values. */
|
|
3
|
+
export declare const DEFAULT_SEPARATOR = "=";
|
|
4
|
+
/** The default character used as comment delimiter. */
|
|
5
|
+
export declare const DEFAULT_COMMENT_DELIMITER = "#";
|
|
6
|
+
/** Characters that can be used as key-value pair separators. */
|
|
7
|
+
export type KeyValuePairSeparator = ' ' | ':' | '=';
|
|
8
|
+
/** Characters that can be used as comment delimiters. */
|
|
9
|
+
export type CommentDelimiter = '#' | '!';
|
|
10
|
+
/** Options on the `Properties.insert` method. */
|
|
11
|
+
export type InsertOptions = {
|
|
12
|
+
/** The name of the key to insert before or after. If the key not found, the new property will not be inserted. */
|
|
13
|
+
referenceKey?: string;
|
|
14
|
+
/** The position of the insertion related to the `referenceKey` (default is `after`) */
|
|
15
|
+
position?: 'before' | 'after';
|
|
16
|
+
/** Escape unicode characters into ISO-8859-1 compatible encoding? */
|
|
17
|
+
escapeUnicode?: boolean;
|
|
18
|
+
/** The key/value separator character. */
|
|
19
|
+
separator?: KeyValuePairSeparator;
|
|
20
|
+
/** A comment to insert before. */
|
|
21
|
+
comment?: string;
|
|
22
|
+
/** The comment's delimiter. */
|
|
23
|
+
commentDelimiter?: CommentDelimiter;
|
|
24
|
+
};
|
|
25
|
+
/** Options on the `Properties.insertComment` method. */
|
|
26
|
+
export type InsertCommentOptions = {
|
|
27
|
+
/** The name of the key to insert before or after. If the key not found, the new property will not be inserted. */
|
|
28
|
+
referenceKey?: string;
|
|
29
|
+
/** The position of the insertion related to the `referenceKey` (default is `after`) */
|
|
30
|
+
position?: 'before' | 'after';
|
|
31
|
+
/** The comment's delimiter. */
|
|
32
|
+
commentDelimiter?: CommentDelimiter;
|
|
33
|
+
};
|
|
34
|
+
/** Options on the `Properties.edit` method. */
|
|
35
|
+
export type EditOptions = {
|
|
36
|
+
/** Optionally replace the existing value with a new value. */
|
|
37
|
+
newValue?: string;
|
|
38
|
+
/** Optionally replace the existing key with a new key name. */
|
|
39
|
+
newKey?: string;
|
|
40
|
+
/** Escape unicode characters into ISO-8859-1 compatible encoding? */
|
|
41
|
+
escapeUnicode?: boolean;
|
|
42
|
+
/** A key/value separator character. */
|
|
43
|
+
separator?: ' ' | ':' | '=';
|
|
44
|
+
/** Optionally insert a new comment, or replace the existing one (including white-space characters). */
|
|
45
|
+
newComment?: string;
|
|
46
|
+
/** The comment's delimiter. */
|
|
47
|
+
commentDelimiter?: CommentDelimiter;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* A .properties file editor.
|
|
51
|
+
*/
|
|
52
|
+
export declare class PropertiesEditor extends Properties {
|
|
53
|
+
/**
|
|
54
|
+
* Create `PropertiesEditor` object.
|
|
55
|
+
*
|
|
56
|
+
* @param content - The content of a `.properties` file.
|
|
57
|
+
*/
|
|
58
|
+
constructor(content: string);
|
|
59
|
+
/**
|
|
60
|
+
* Insert a new property in the existing object (by default it will be at the end).
|
|
61
|
+
*
|
|
62
|
+
* @param key - A property key (unescaped).
|
|
63
|
+
* @param value - A property value (unescaped).
|
|
64
|
+
* @param options - Additional options.
|
|
65
|
+
*/
|
|
66
|
+
insert(key: string, value: string, options?: InsertOptions): void;
|
|
67
|
+
/**
|
|
68
|
+
* Insert a new comment in the existing object (by default it will be at the end).
|
|
69
|
+
*
|
|
70
|
+
* @param comment - The comment to add.
|
|
71
|
+
* @param options - Additional options.
|
|
72
|
+
*/
|
|
73
|
+
insertComment(comment: string, options?: InsertCommentOptions): void;
|
|
74
|
+
/**
|
|
75
|
+
* Remove the last occurrence of a given key from the existing object.
|
|
76
|
+
*
|
|
77
|
+
* @param key - The name of the key to remove.
|
|
78
|
+
* @param removeCommentsAndWhiteSpace - By default, comments and white-space characters before the key will be removed.
|
|
79
|
+
*/
|
|
80
|
+
remove(key: string, removeCommentsAndWhiteSpace?: boolean): void;
|
|
81
|
+
/**
|
|
82
|
+
* Restore the original newline characters of a key.
|
|
83
|
+
*
|
|
84
|
+
* @param property - A property object.
|
|
85
|
+
*
|
|
86
|
+
* @returns The key with its original newlines characters restored.
|
|
87
|
+
*/
|
|
88
|
+
private getKeyWithNewlines;
|
|
89
|
+
/**
|
|
90
|
+
* Restore the original newline characters of a value.
|
|
91
|
+
*
|
|
92
|
+
* @param property - A property object.
|
|
93
|
+
*
|
|
94
|
+
* @returns The value with its original newlines characters restored.
|
|
95
|
+
*/
|
|
96
|
+
private getValueWithNewlines;
|
|
97
|
+
/**
|
|
98
|
+
* Edit the last occurrence of a given key from the existing object.
|
|
99
|
+
*
|
|
100
|
+
* @param key - The name of the key to edit.
|
|
101
|
+
* @param options - Additional options.
|
|
102
|
+
*/
|
|
103
|
+
edit(key: string, options?: EditOptions): void;
|
|
104
|
+
}
|