properties-file 3.3.18 → 3.4.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/LICENSE +21 -21
- package/README.md +281 -281
- package/lib/cjs/editor/index.js +1 -0
- package/lib/{escape → cjs/escape}/index.d.ts +2 -2
- package/lib/cjs/escape/index.js +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/loader/webpack.js +1 -0
- package/lib/cjs/package.json +1 -0
- package/lib/{properties-file.d.ts → cjs/properties-file.d.ts} +7 -7
- package/lib/cjs/properties.js +1 -0
- package/lib/{property-line.d.ts → cjs/property-line.d.ts} +1 -1
- package/lib/cjs/property-line.js +1 -0
- package/lib/cjs/property.js +1 -0
- package/lib/cjs/unescape/index.js +1 -0
- package/lib/esm/editor/index.d.ts +139 -0
- package/lib/esm/editor/index.js +1 -0
- package/lib/esm/escape/index.d.ts +18 -0
- package/lib/esm/escape/index.js +1 -0
- package/lib/esm/index.d.ts +17 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/loader/webpack.d.ts +10 -0
- package/lib/esm/loader/webpack.js +1 -0
- package/lib/esm/properties-file.d.ts +7 -0
- package/lib/esm/properties.d.ts +95 -0
- package/lib/esm/properties.js +1 -0
- package/lib/esm/property-line.d.ts +22 -0
- package/lib/esm/property-line.js +1 -0
- package/lib/esm/property.d.ts +83 -0
- package/lib/esm/property.js +1 -0
- package/lib/esm/unescape/index.d.ts +11 -0
- package/lib/esm/unescape/index.js +1 -0
- package/package.json +132 -100
- package/lib/editor/index.js +0 -74
- package/lib/escape/index.js +0 -23
- package/lib/index.js +0 -9
- package/lib/loader/webpack.js +0 -7
- package/lib/properties.js +0 -52
- package/lib/property-line.js +0 -9
- package/lib/property.js +0 -39
- package/lib/unescape/index.js +0 -17
- /package/lib/{editor → cjs/editor}/index.d.ts +0 -0
- /package/lib/{index.d.ts → cjs/index.d.ts} +0 -0
- /package/lib/{loader → cjs/loader}/webpack.d.ts +0 -0
- /package/lib/{properties.d.ts → cjs/properties.d.ts} +0 -0
- /package/lib/{property.d.ts → cjs/property.d.ts} +0 -0
- /package/lib/{unescape → cjs/unescape}/index.d.ts +0 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 Avansai
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Avansai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,281 +1,281 @@
|
|
|
1
|
-
# properties-file
|
|
2
|
-
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
4
|
-
[](https://www.npmjs.com/package/properties-file)
|
|
5
|
-

|
|
6
|
-

|
|
7
|
-
|
|
8
|
-
`.properties` file parser, editor, formatter and Webpack loader.
|
|
9
|
-
|
|
10
|
-
## Installation 💻
|
|
11
|
-
|
|
12
|
-
> ⚠ In April 2023, we released version 3 of this package, which includes breaking changes. Please refer to the [upgrade guide](./V2-TO-V3-UPGRADE-GUIDE.md) before upgrading.
|
|
13
|
-
|
|
14
|
-
Add the package as a dependency:
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
npm install properties-file
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## What's in it for me? 🤔
|
|
21
|
-
|
|
22
|
-
- A modern library written entirely in TypeScript that exactly reproduces the [Properties Java implementation](/assets/java-implementation.md).
|
|
23
|
-
- Works for both Node.js applications and browsers that support at least [ES5](https://www.w3schools.com/js/js_es5.asp).
|
|
24
|
-
- Flexible APIs:
|
|
25
|
-
- `getProperties` converts the content of `.properties` files to a key-value pair object.
|
|
26
|
-
- A `Properties` class provides insights into parsing data.
|
|
27
|
-
- A `PropertiesEditor` class enables the addition, edition, and removal of entries.
|
|
28
|
-
- `escapeKey` and `escapeValue` allow the conversion of any content to a `.properties` compatible format.
|
|
29
|
-
- The library also includes a Webpack loader to import `.properties` files directly into your application.
|
|
30
|
-
- Tiny ([under 4kB compressed](https://bundlephobia.com/package/properties-file)) with 0 dependencies.
|
|
31
|
-
- 100% test coverage based on the output from a Java implementation.
|
|
32
|
-
- Active maintenance (many popular `.properties` packages have been inactive for years).
|
|
33
|
-
|
|
34
|
-
## Usage 🎬
|
|
35
|
-
|
|
36
|
-
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.
|
|
37
|
-
|
|
38
|
-
### `getProperties` (converting `.properties` to an object)
|
|
39
|
-
|
|
40
|
-
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:
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
import { readFileSync } from 'node:fs'
|
|
44
|
-
import { getProperties } from 'properties-file'
|
|
45
|
-
|
|
46
|
-
console.log(getProperties(readFileSync('hello-world.properties')))
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Output:
|
|
50
|
-
|
|
51
|
-
```js
|
|
52
|
-
{ hello: 'hello', world: 'world' }
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### `Properties` (using parsing metadata)
|
|
56
|
-
|
|
57
|
-
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:
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
import { Properties } from 'properties-file'
|
|
61
|
-
|
|
62
|
-
const properties = new Properties(
|
|
63
|
-
'hello = hello1\nworld = world1\nworld = world2\nhello = hello2\nworld = world3'
|
|
64
|
-
)
|
|
65
|
-
console.log(properties.format())
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Outputs:
|
|
69
|
-
*
|
|
70
|
-
* hello = hello1
|
|
71
|
-
* world = world1
|
|
72
|
-
* world = world2
|
|
73
|
-
* hello = hello2
|
|
74
|
-
* world = world3
|
|
75
|
-
*/
|
|
76
|
-
|
|
77
|
-
properties.collection.forEach((property) => {
|
|
78
|
-
console.log(`${property.key} = ${property.value}`)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Outputs:
|
|
83
|
-
*
|
|
84
|
-
* hello = hello2
|
|
85
|
-
* world = world3
|
|
86
|
-
*/
|
|
87
|
-
|
|
88
|
-
const keyCollisions = properties.getKeyCollisions()
|
|
89
|
-
|
|
90
|
-
keyCollisions.forEach((keyCollision) => {
|
|
91
|
-
console.warn(
|
|
92
|
-
`Found a key collision for key '${
|
|
93
|
-
keyCollision.key
|
|
94
|
-
}' on lines ${keyCollision.startingLineNumbers.join(
|
|
95
|
-
', '
|
|
96
|
-
)} (will use the value at line ${keyCollision.getApplicableLineNumber()}).`
|
|
97
|
-
)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Outputs:
|
|
102
|
-
*
|
|
103
|
-
* Found a key collision for key 'hello' on lines 1, 4 (will use the value at line 4).
|
|
104
|
-
* Found a key collision for key 'world' on lines 2, 3, 5 (will use the value at line 5).
|
|
105
|
-
*/
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
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.
|
|
109
|
-
|
|
110
|
-
### `PropertiesEditor` (editing `.properties` content)
|
|
111
|
-
|
|
112
|
-
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:
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
import { Properties } from 'properties-file'
|
|
116
|
-
import { escapeKey, escapeValue } from 'properties-file/escape'
|
|
117
|
-
|
|
118
|
-
const properties = new Properties('hello = hello\n# This is a comment\nworld = world')
|
|
119
|
-
const newProperties: string[] = []
|
|
120
|
-
|
|
121
|
-
properties.collection.forEach((property) => {
|
|
122
|
-
const key = property.key === 'world' ? 'new world' : property.key
|
|
123
|
-
const value = property.value === 'world' ? 'new world' : property.value
|
|
124
|
-
newProperties.push(`${escapeKey(key)} = ${escapeValue(value)}`)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
console.log(newProperties.join('\n'))
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Outputs:
|
|
131
|
-
*
|
|
132
|
-
* hello = hello
|
|
133
|
-
* new\ world = new world
|
|
134
|
-
*/
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
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.
|
|
138
|
-
|
|
139
|
-
```ts
|
|
140
|
-
import { PropertiesEditor } from 'properties-file/editor'
|
|
141
|
-
|
|
142
|
-
const properties = new PropertiesEditor('hello = hello\n# This is a comment\nworld = world')
|
|
143
|
-
console.log(properties.format())
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Outputs:
|
|
147
|
-
*
|
|
148
|
-
* hello = hello
|
|
149
|
-
* # This is a comment
|
|
150
|
-
* world = world
|
|
151
|
-
*/
|
|
152
|
-
|
|
153
|
-
properties.insertComment('This is a multiline\ncomment before `newKey3`')
|
|
154
|
-
properties.insert('newKey3', 'This is my third key')
|
|
155
|
-
|
|
156
|
-
properties.insert('newKey1', 'This is my first new key', {
|
|
157
|
-
referenceKey: 'newKey3',
|
|
158
|
-
position: 'before',
|
|
159
|
-
comment: 'Below are the new keys being edited',
|
|
160
|
-
commentDelimiter: '!',
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
properties.insert('newKey2', 'こんにちは', {
|
|
164
|
-
referenceKey: 'newKey1',
|
|
165
|
-
position: 'after',
|
|
166
|
-
escapeUnicode: true,
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
properties.delete('hello')
|
|
170
|
-
properties.update('world', {
|
|
171
|
-
newValue: 'new world',
|
|
172
|
-
})
|
|
173
|
-
console.log(properties.format())
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Outputs:
|
|
177
|
-
*
|
|
178
|
-
* # This is a comment
|
|
179
|
-
* world = new world
|
|
180
|
-
* ! Below are the new keys being edited
|
|
181
|
-
* newKey1 = This is my first new key
|
|
182
|
-
* newKey2 = \u3053\u3093\u306b\u3061\u306f
|
|
183
|
-
* # This is a multiline
|
|
184
|
-
* # comment before `newKey3`
|
|
185
|
-
* newKey3 = This is my third key
|
|
186
|
-
*/
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
For convenience, we also added an `upsert` method that allows updating a key if it exists or adding it at the end, when it doesn't. Make sure to check in your IDE for all available methods and options in our TSDoc.
|
|
190
|
-
|
|
191
|
-
### Webpack File Loader
|
|
192
|
-
|
|
193
|
-
If you would like to import `.properties` directly using `import`, this package comes with its own Webpack file loader located under `properties-file/webpack-loader`. Here is an example of how to configure it:
|
|
194
|
-
|
|
195
|
-
```js
|
|
196
|
-
// webpack.config.js
|
|
197
|
-
module.exports = {
|
|
198
|
-
module: {
|
|
199
|
-
rules: [
|
|
200
|
-
{
|
|
201
|
-
test: /\.properties$/i,
|
|
202
|
-
use: [
|
|
203
|
-
{
|
|
204
|
-
loader: 'properties-file/webpack-loader',
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
},
|
|
208
|
-
],
|
|
209
|
-
},
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
As soon as you configure Webpack, the `.properties` type should be available in your IDE when using `import`. If you ever need to add it manually, you can add a `*.properties` type declaration file at the root of your application, like this:
|
|
214
|
-
|
|
215
|
-
```js
|
|
216
|
-
// properties-file.d.ts
|
|
217
|
-
declare module '*.properties' {
|
|
218
|
-
const properties: { readonly [key: string]: string };
|
|
219
|
-
export default properties;
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
By adding these configurations you should now be able to import directly `.properties` files just like this:
|
|
224
|
-
|
|
225
|
-
```ts
|
|
226
|
-
import helloWorld from './hello-world.properties'
|
|
227
|
-
|
|
228
|
-
console.dir(helloWorld)
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
Output:
|
|
232
|
-
|
|
233
|
-
```json
|
|
234
|
-
{ "hello": "world" }
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
## Why another `.properties` file package?
|
|
238
|
-
|
|
239
|
-
There are probably over 20 similar packages available, but:
|
|
240
|
-
|
|
241
|
-
- Many of the most popular packages have had no activity for over 5 years.
|
|
242
|
-
- Most packages will not replicate the current Java implementation.
|
|
243
|
-
- No package offers the same capabilities as this one.
|
|
244
|
-
|
|
245
|
-
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.
|
|
246
|
-
|
|
247
|
-
### So why `.properties` files?
|
|
248
|
-
|
|
249
|
-
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):
|
|
250
|
-
|
|
251
|
-
| File format | Key/value based | Supports inline comments | Built for localization | Good linguistic tools support |
|
|
252
|
-
| ------------- | ---------------- | ------------------------ | ---------------------- | ----------------------------- |
|
|
253
|
-
| `.properties` | Yes | Yes | Yes (Resource Bundles) | Yes |
|
|
254
|
-
| `JSON` | No (can do more) | No (requires JSON5) | No | Depends on the schema |
|
|
255
|
-
| `YAML` | No (can do more) | Yes | No | Depends on the schema |
|
|
256
|
-
|
|
257
|
-
Having good JavaScript/TypeScript support for `.properties` files offers more internationalization (i18n) options.
|
|
258
|
-
|
|
259
|
-
### How does this package work?
|
|
260
|
-
|
|
261
|
-
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:
|
|
262
|
-
|
|
263
|
-
1. The content is split by lines, creating an array of strings where each line is an element.
|
|
264
|
-
2. All lines are parsed to create a collection of `Property` objects that:
|
|
265
|
-
1. Identify key-value pair lines from the other lines (e.g., comments, blank lines, etc.).
|
|
266
|
-
2. Merge back multiline key-value pairs on single lines by removing trailing backslashes.
|
|
267
|
-
3. Unescape the keys and values.
|
|
268
|
-
|
|
269
|
-
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.
|
|
270
|
-
|
|
271
|
-
## Additional references
|
|
272
|
-
|
|
273
|
-
- Java [Test Sandbox](https://codehs.com/sandbox/id/java-main-FObePj)
|
|
274
|
-
- Java's `Properties` class [documentation](https://docs.oracle.com/javase/9/docs/api/java/util/Properties.html)
|
|
275
|
-
- Java's `PropertyResourceBundle` [documentation](https://docs.oracle.com/javase/9/docs/api/java/util/PropertyResourceBundle.html)
|
|
276
|
-
- Java's Internationalization [Guide](https://docs.oracle.com/en/java/javase/18/intl/internationalization-overview.html)
|
|
277
|
-
- Wikipedia's .properties [page](https://en.wikipedia.org/wiki/.properties)
|
|
278
|
-
|
|
279
|
-
### Special mention
|
|
280
|
-
|
|
281
|
-
Thanks to [@calibr](https://github.com/calibr), the creator of [properties-file version 1.0](https://github.com/calibr/properties-file), for letting us use the [https://www.npmjs.com/package/properties-file](https://www.npmjs.com/package/properties-file) package name. We hope that it will make it easier to find our package.
|
|
1
|
+
# properties-file
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.npmjs.com/package/properties-file)
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
`.properties` file parser, editor, formatter and Webpack loader.
|
|
9
|
+
|
|
10
|
+
## Installation 💻
|
|
11
|
+
|
|
12
|
+
> ⚠ In April 2023, we released version 3 of this package, which includes breaking changes. Please refer to the [upgrade guide](./V2-TO-V3-UPGRADE-GUIDE.md) before upgrading.
|
|
13
|
+
|
|
14
|
+
Add the package as a dependency:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npm install properties-file
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## What's in it for me? 🤔
|
|
21
|
+
|
|
22
|
+
- A modern library written entirely in TypeScript that exactly reproduces the [Properties Java implementation](/assets/java-implementation.md).
|
|
23
|
+
- Works for both Node.js applications and browsers that support at least [ES5](https://www.w3schools.com/js/js_es5.asp).
|
|
24
|
+
- Flexible APIs:
|
|
25
|
+
- `getProperties` converts the content of `.properties` files to a key-value pair object.
|
|
26
|
+
- A `Properties` class provides insights into parsing data.
|
|
27
|
+
- A `PropertiesEditor` class enables the addition, edition, and removal of entries.
|
|
28
|
+
- `escapeKey` and `escapeValue` allow the conversion of any content to a `.properties` compatible format.
|
|
29
|
+
- The library also includes a Webpack loader to import `.properties` files directly into your application.
|
|
30
|
+
- Tiny ([under 4kB compressed](https://bundlephobia.com/package/properties-file)) with 0 dependencies.
|
|
31
|
+
- 100% test coverage based on the output from a Java implementation.
|
|
32
|
+
- Active maintenance (many popular `.properties` packages have been inactive for years).
|
|
33
|
+
|
|
34
|
+
## Usage 🎬
|
|
35
|
+
|
|
36
|
+
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.
|
|
37
|
+
|
|
38
|
+
### `getProperties` (converting `.properties` to an object)
|
|
39
|
+
|
|
40
|
+
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:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { readFileSync } from 'node:fs'
|
|
44
|
+
import { getProperties } from 'properties-file'
|
|
45
|
+
|
|
46
|
+
console.log(getProperties(readFileSync('hello-world.properties')))
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Output:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
{ hello: 'hello', world: 'world' }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `Properties` (using parsing metadata)
|
|
56
|
+
|
|
57
|
+
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:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { Properties } from 'properties-file'
|
|
61
|
+
|
|
62
|
+
const properties = new Properties(
|
|
63
|
+
'hello = hello1\nworld = world1\nworld = world2\nhello = hello2\nworld = world3'
|
|
64
|
+
)
|
|
65
|
+
console.log(properties.format())
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Outputs:
|
|
69
|
+
*
|
|
70
|
+
* hello = hello1
|
|
71
|
+
* world = world1
|
|
72
|
+
* world = world2
|
|
73
|
+
* hello = hello2
|
|
74
|
+
* world = world3
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
properties.collection.forEach((property) => {
|
|
78
|
+
console.log(`${property.key} = ${property.value}`)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Outputs:
|
|
83
|
+
*
|
|
84
|
+
* hello = hello2
|
|
85
|
+
* world = world3
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
const keyCollisions = properties.getKeyCollisions()
|
|
89
|
+
|
|
90
|
+
keyCollisions.forEach((keyCollision) => {
|
|
91
|
+
console.warn(
|
|
92
|
+
`Found a key collision for key '${
|
|
93
|
+
keyCollision.key
|
|
94
|
+
}' on lines ${keyCollision.startingLineNumbers.join(
|
|
95
|
+
', '
|
|
96
|
+
)} (will use the value at line ${keyCollision.getApplicableLineNumber()}).`
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Outputs:
|
|
102
|
+
*
|
|
103
|
+
* Found a key collision for key 'hello' on lines 1, 4 (will use the value at line 4).
|
|
104
|
+
* Found a key collision for key 'world' on lines 2, 3, 5 (will use the value at line 5).
|
|
105
|
+
*/
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
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.
|
|
109
|
+
|
|
110
|
+
### `PropertiesEditor` (editing `.properties` content)
|
|
111
|
+
|
|
112
|
+
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:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { Properties } from 'properties-file'
|
|
116
|
+
import { escapeKey, escapeValue } from 'properties-file/escape'
|
|
117
|
+
|
|
118
|
+
const properties = new Properties('hello = hello\n# This is a comment\nworld = world')
|
|
119
|
+
const newProperties: string[] = []
|
|
120
|
+
|
|
121
|
+
properties.collection.forEach((property) => {
|
|
122
|
+
const key = property.key === 'world' ? 'new world' : property.key
|
|
123
|
+
const value = property.value === 'world' ? 'new world' : property.value
|
|
124
|
+
newProperties.push(`${escapeKey(key)} = ${escapeValue(value)}`)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
console.log(newProperties.join('\n'))
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Outputs:
|
|
131
|
+
*
|
|
132
|
+
* hello = hello
|
|
133
|
+
* new\ world = new world
|
|
134
|
+
*/
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
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.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { PropertiesEditor } from 'properties-file/editor'
|
|
141
|
+
|
|
142
|
+
const properties = new PropertiesEditor('hello = hello\n# This is a comment\nworld = world')
|
|
143
|
+
console.log(properties.format())
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Outputs:
|
|
147
|
+
*
|
|
148
|
+
* hello = hello
|
|
149
|
+
* # This is a comment
|
|
150
|
+
* world = world
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
properties.insertComment('This is a multiline\ncomment before `newKey3`')
|
|
154
|
+
properties.insert('newKey3', 'This is my third key')
|
|
155
|
+
|
|
156
|
+
properties.insert('newKey1', 'This is my first new key', {
|
|
157
|
+
referenceKey: 'newKey3',
|
|
158
|
+
position: 'before',
|
|
159
|
+
comment: 'Below are the new keys being edited',
|
|
160
|
+
commentDelimiter: '!',
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
properties.insert('newKey2', 'こんにちは', {
|
|
164
|
+
referenceKey: 'newKey1',
|
|
165
|
+
position: 'after',
|
|
166
|
+
escapeUnicode: true,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
properties.delete('hello')
|
|
170
|
+
properties.update('world', {
|
|
171
|
+
newValue: 'new world',
|
|
172
|
+
})
|
|
173
|
+
console.log(properties.format())
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Outputs:
|
|
177
|
+
*
|
|
178
|
+
* # This is a comment
|
|
179
|
+
* world = new world
|
|
180
|
+
* ! Below are the new keys being edited
|
|
181
|
+
* newKey1 = This is my first new key
|
|
182
|
+
* newKey2 = \u3053\u3093\u306b\u3061\u306f
|
|
183
|
+
* # This is a multiline
|
|
184
|
+
* # comment before `newKey3`
|
|
185
|
+
* newKey3 = This is my third key
|
|
186
|
+
*/
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
For convenience, we also added an `upsert` method that allows updating a key if it exists or adding it at the end, when it doesn't. Make sure to check in your IDE for all available methods and options in our TSDoc.
|
|
190
|
+
|
|
191
|
+
### Webpack File Loader
|
|
192
|
+
|
|
193
|
+
If you would like to import `.properties` directly using `import`, this package comes with its own Webpack file loader located under `properties-file/webpack-loader`. Here is an example of how to configure it:
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
// webpack.config.js
|
|
197
|
+
module.exports = {
|
|
198
|
+
module: {
|
|
199
|
+
rules: [
|
|
200
|
+
{
|
|
201
|
+
test: /\.properties$/i,
|
|
202
|
+
use: [
|
|
203
|
+
{
|
|
204
|
+
loader: 'properties-file/webpack-loader',
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
As soon as you configure Webpack, the `.properties` type should be available in your IDE when using `import`. If you ever need to add it manually, you can add a `*.properties` type declaration file at the root of your application, like this:
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
// properties-file.d.ts
|
|
217
|
+
declare module '*.properties' {
|
|
218
|
+
const properties: { readonly [key: string]: string };
|
|
219
|
+
export default properties;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
By adding these configurations you should now be able to import directly `.properties` files just like this:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import helloWorld from './hello-world.properties'
|
|
227
|
+
|
|
228
|
+
console.dir(helloWorld)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Output:
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{ "hello": "world" }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Why another `.properties` file package?
|
|
238
|
+
|
|
239
|
+
There are probably over 20 similar packages available, but:
|
|
240
|
+
|
|
241
|
+
- Many of the most popular packages have had no activity for over 5 years.
|
|
242
|
+
- Most packages will not replicate the current Java implementation.
|
|
243
|
+
- No package offers the same capabilities as this one.
|
|
244
|
+
|
|
245
|
+
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.
|
|
246
|
+
|
|
247
|
+
### So why `.properties` files?
|
|
248
|
+
|
|
249
|
+
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):
|
|
250
|
+
|
|
251
|
+
| File format | Key/value based | Supports inline comments | Built for localization | Good linguistic tools support |
|
|
252
|
+
| ------------- | ---------------- | ------------------------ | ---------------------- | ----------------------------- |
|
|
253
|
+
| `.properties` | Yes | Yes | Yes (Resource Bundles) | Yes |
|
|
254
|
+
| `JSON` | No (can do more) | No (requires JSON5) | No | Depends on the schema |
|
|
255
|
+
| `YAML` | No (can do more) | Yes | No | Depends on the schema |
|
|
256
|
+
|
|
257
|
+
Having good JavaScript/TypeScript support for `.properties` files offers more internationalization (i18n) options.
|
|
258
|
+
|
|
259
|
+
### How does this package work?
|
|
260
|
+
|
|
261
|
+
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:
|
|
262
|
+
|
|
263
|
+
1. The content is split by lines, creating an array of strings where each line is an element.
|
|
264
|
+
2. All lines are parsed to create a collection of `Property` objects that:
|
|
265
|
+
1. Identify key-value pair lines from the other lines (e.g., comments, blank lines, etc.).
|
|
266
|
+
2. Merge back multiline key-value pairs on single lines by removing trailing backslashes.
|
|
267
|
+
3. Unescape the keys and values.
|
|
268
|
+
|
|
269
|
+
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.
|
|
270
|
+
|
|
271
|
+
## Additional references
|
|
272
|
+
|
|
273
|
+
- Java [Test Sandbox](https://codehs.com/sandbox/id/java-main-FObePj)
|
|
274
|
+
- Java's `Properties` class [documentation](https://docs.oracle.com/javase/9/docs/api/java/util/Properties.html)
|
|
275
|
+
- Java's `PropertyResourceBundle` [documentation](https://docs.oracle.com/javase/9/docs/api/java/util/PropertyResourceBundle.html)
|
|
276
|
+
- Java's Internationalization [Guide](https://docs.oracle.com/en/java/javase/18/intl/internationalization-overview.html)
|
|
277
|
+
- Wikipedia's .properties [page](https://en.wikipedia.org/wiki/.properties)
|
|
278
|
+
|
|
279
|
+
### Special mention
|
|
280
|
+
|
|
281
|
+
Thanks to [@calibr](https://github.com/calibr), the creator of [properties-file version 1.0](https://github.com/calibr/properties-file), for letting us use the [https://www.npmjs.com/package/properties-file](https://www.npmjs.com/package/properties-file) package name. We hope that it will make it easier to find our package.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var __extends=this&&this.__extends||function(){var e=function(r,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,r){e.__proto__=r}||function(e,r){for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])},e(r,n)};return function(r,n){if("function"!=typeof n&&null!==n)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");function t(){this.constructor=r}e(r,n),r.prototype=null===n?Object.create(n):(t.prototype=n.prototype,new t)}}(),__read=this&&this.__read||function(e,r){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var t,i,o=n.call(e),s=[];try{for(;(void 0===r||r-- >0)&&!(t=o.next()).done;)s.push(t.value)}catch(e){i={error:e}}finally{try{t&&!t.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return s},__spreadArray=this&&this.__spreadArray||function(e,r,n){if(n||2===arguments.length)for(var t,i=0,o=r.length;i<o;i++)!t&&i in r||(t||(t=Array.prototype.slice.call(r,0,i)),t[i]=r[i]);return e.concat(t||Array.prototype.slice.call(r))};Object.defineProperty(exports,"__esModule",{value:!0}),exports.PropertiesEditor=exports.DEFAULT_COMMENT_DELIMITER=exports.DEFAULT_SEPARATOR=void 0;var escape_1=require("../escape"),properties_1=require("../properties");exports.DEFAULT_SEPARATOR="=",exports.DEFAULT_COMMENT_DELIMITER="#";var PropertiesEditor=function(e){function r(r){var n=e.call(this,r)||this;return n.needsLineParsing=!1,n}return __extends(r,e),r.prototype.parseLinesIfNeeded=function(){this.needsLineParsing&&(this.parseLines(),this.needsLineParsing=!1)},r.prototype.insert=function(e,r,n){var t,i,o,s=(null==n?void 0:n.escapeUnicode)||!1,a=(null==n?void 0:n.separator)?" "===n.separator?" ":" ".concat(n.separator," "):" ".concat(exports.DEFAULT_SEPARATOR," ").replace(" "," "),c=null==n?void 0:n.referenceKey,l=(null==n?void 0:n.position)||"after";c&&this.parseLinesIfNeeded();var p=e.split(/\r?\n/).map((function(e){return(0,escape_1.escapeKey)(e,s)})).join("\\\n"),d=r.split(/\r?\n/).map((function(e){return(0,escape_1.escapeValue)(e,s)})).join("\\\n"),u="".concat((null==n?void 0:n.commentDelimiter)||exports.DEFAULT_COMMENT_DELIMITER," "),_=void 0===(null==n?void 0:n.comment)?"":"".concat("".concat(u).concat(n.comment).split(/\r?\n/).join("\n".concat(u)),"\n"),v="".concat(_).concat(p).concat(a).concat(d).split(/\n/);if(void 0===c)return(t=this.lines).push.apply(t,__spreadArray([],__read(v),!1)),this.needsLineParsing=!0,!0;var y=__spreadArray([],__read(this.collection),!1).reverse().find((function(e){return e.key===c}));if(y){var f="after"===l?y.endingLineNumber:null!==(o=null===(i=y.previousProperty)||void 0===i?void 0:i.endingLineNumber)&&void 0!==o?o:0;return this.lines=__spreadArray(__spreadArray(__spreadArray([],__read(this.lines.slice(0,f)),!1),__read(v),!1),__read(this.lines.slice(f)),!1),this.needsLineParsing=!0,!0}return!1},r.prototype.insertComment=function(e,r){var n,t,i,o=null==r?void 0:r.referenceKey,s=(null==r?void 0:r.position)||"after";o&&this.parseLinesIfNeeded();var a="".concat((null==r?void 0:r.commentDelimiter)||exports.DEFAULT_COMMENT_DELIMITER," "),c="".concat(a).concat(e).replace(/\r?\n/g,"\n".concat(a)).split(/\n/);if(void 0===o)return(n=this.lines).push.apply(n,__spreadArray([],__read(c),!1)),this.needsLineParsing=!0,!0;var l=__spreadArray([],__read(this.collection),!1).reverse().find((function(e){return e.key===o}));if(l){var p="after"===s?l.endingLineNumber:null!==(i=null===(t=l.previousProperty)||void 0===t?void 0:t.endingLineNumber)&&void 0!==i?i:0;return this.lines=__spreadArray(__spreadArray(__spreadArray([],__read(this.lines.slice(0,p)),!1),__read(c),!1),__read(this.lines.slice(p)),!1),this.needsLineParsing=!0,!0}return!1},r.prototype.delete=function(e,r){var n,t;void 0===r&&(r=!0),this.parseLinesIfNeeded();var i=__spreadArray([],__read(this.collection),!1).reverse().find((function(r){return r.key===e}));if(i){var o=r?null!==(t=null===(n=i.previousProperty)||void 0===n?void 0:n.endingLineNumber)&&void 0!==t?t:0:i.startingLineNumber-1,s=i.endingLineNumber;return this.lines=__spreadArray(__spreadArray([],__read(this.lines.slice(0,o)),!1),__read(this.lines.slice(s)),!1),this.needsLineParsing=!0,!0}return!1},r.prototype.getKeyWithNewlines=function(e){return 0===e.newlinePositions.length?e.key:__spreadArray([],__read(e.key),!1).reduce((function(r,n,t){return"".concat(r).concat(e.newlinePositions.includes(t)?"\n":"").concat(n)}),"")},r.prototype.getValueWithNewlines=function(e){return 0===e.newlinePositions.length||void 0===e.valuePosition?e.value:__spreadArray([],__read(e.value),!1).reduce((function(r,n,t){return"".concat(r).concat(e.newlinePositions.includes(t+e.valuePosition)?"\n":"").concat(n)}),"")},r.prototype.update=function(e,r){var n,t,i,o;this.parseLinesIfNeeded();var s=__spreadArray([],__read(this.collection),!1).reverse().find((function(r){return r.key===e}));if(!s||!r)return!1;var a=r.escapeUnicode||!1,c=r.separator?" "===r.separator?" ":" ".concat(r.separator," "):s.separator||" ".concat(exports.DEFAULT_SEPARATOR," ").replace(" "," "),l=(null!==(n=r.newKey)&&void 0!==n?n:this.getKeyWithNewlines(s)).split(/\r?\n/).map((function(e){return(0,escape_1.escapeKey)(e,a)})).join("\\\n"),p=(null!==(t=r.newValue)&&void 0!==t?t:this.getValueWithNewlines(s)).split(/\r?\n/).map((function(e){return(0,escape_1.escapeValue)(e,a)})).join("\\\n"),d="".concat(r.commentDelimiter||exports.DEFAULT_COMMENT_DELIMITER," "),u=void 0===r.newComment?"":"".concat("".concat(d).concat(r.newComment).split(/\r?\n/).join("\n".concat(d)),"\n"),_="".concat(u).concat(l).concat(c).concat(p).split(/\n/);return this.lines=__spreadArray(__spreadArray(__spreadArray([],__read(this.lines.slice(0,void 0===r.newComment?s.startingLineNumber-1:null!==(o=null===(i=s.previousProperty)||void 0===i?void 0:i.endingLineNumber)&&void 0!==o?o:0)),!1),__read(_),!1),__read(this.lines.slice(s.endingLineNumber)),!1),this.needsLineParsing=!0,!0},r.prototype.upsert=function(e,r,n){return this.parseLinesIfNeeded(),this.keyLineNumbers[e]?this.update(e,{newValue:r,newComment:null==n?void 0:n.comment,commentDelimiter:null==n?void 0:n.commentDelimiter,separator:null==n?void 0:n.separator,escapeUnicode:null==n?void 0:n.escapeUnicode}):this.insert(e,r,n)},r}(properties_1.Properties);exports.PropertiesEditor=PropertiesEditor;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @param unescapedKey - A property key to be escaped.
|
|
5
5
|
* @param escapeUnicode - Escape unicode characters into ISO-8859-1 compatible encoding?
|
|
6
6
|
*
|
|
7
|
-
* @
|
|
7
|
+
* @returns The escaped key.
|
|
8
8
|
*/
|
|
9
9
|
export declare const escapeKey: (unescapedKey: string, escapeUnicode?: boolean) => string;
|
|
10
10
|
/**
|
|
@@ -13,6 +13,6 @@ export declare const escapeKey: (unescapedKey: string, escapeUnicode?: boolean)
|
|
|
13
13
|
* @param unescapedValue - Property value to be escaped.
|
|
14
14
|
* @param escapeUnicode - Escape unicode characters into ISO-8859-1 compatible encoding?
|
|
15
15
|
*
|
|
16
|
-
* @
|
|
16
|
+
* @returns The escaped value.
|
|
17
17
|
*/
|
|
18
18
|
export declare const escapeValue: (unescapedValue: string, escapeUnicode?: boolean) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.escapeValue=exports.escapeKey=void 0;var escapeKey=function(e,t){return void 0===t&&(t=!1),escapeContent(e,!0,t)};exports.escapeKey=escapeKey;var escapeValue=function(e,t){return void 0===t&&(t=!1),escapeContent(e,!1,t)};exports.escapeValue=escapeValue;var escapeContent=function(e,t,r){return e.replace(new RegExp("[\\s!#:=\\\\".concat(r?"\\u0000-\\u001F\\u007F-\\uFFFF":"","]"),"g"),(function(e,r){switch(e){case" ":return t||0===r?"\\ ":" ";case"\\":return"\\\\";case"\f":return"\\f";case"\n":return"\\n";case"\r":return"\\r";case"\t":return"\\t";case"=":case":":case"#":case"!":return"\\".concat(e);default:return"\\u".concat(e.codePointAt(0).toString(16).padStart(4,"0"))}}))};
|
package/lib/cjs/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getProperties=exports.Properties=void 0;var properties_1=require("./properties"),properties_2=require("./properties");Object.defineProperty(exports,"Properties",{enumerable:!0,get:function(){return properties_2.Properties}});var getProperties=function(e){return new properties_1.Properties(e).toObject()};exports.getProperties=getProperties;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var __1=require(".."),webpackLoader=function(e){return"module.exports = ".concat(JSON.stringify((0,__1.getProperties)(e)),";")};exports.default=webpackLoader;
|