yini-parser 1.0.2-beta → 1.1.0-beta
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/CHANGELOG.md +28 -0
- package/README.md +83 -98
- package/dist/YINI.d.ts +34 -11
- package/dist/YINI.js +206 -93
- package/dist/core/ASTBuilder.d.ts +191 -0
- package/dist/core/ASTBuilder.js +827 -0
- package/dist/core/ErrorDataHandler.d.ts +19 -19
- package/dist/core/ErrorDataHandler.js +258 -150
- package/dist/core/objectBuilder.d.ts +9 -3
- package/dist/core/objectBuilder.js +126 -163
- package/dist/core/types.d.ts +234 -44
- package/dist/core/types.js +7 -33
- package/dist/grammar/YiniLexer.d.ts +54 -48
- package/dist/grammar/YiniLexer.js +324 -289
- package/dist/grammar/YiniParser.d.ts +167 -150
- package/dist/grammar/YiniParser.js +1241 -1202
- package/dist/grammar/YiniParserVisitor.d.ts +59 -45
- package/dist/grammar/YiniParserVisitor.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +286 -26
- package/dist/parseEntry.d.ts +3 -2
- package/dist/parseEntry.js +352 -81
- package/dist/parsers/extractHeaderParts.d.ts +3 -2
- package/dist/parsers/extractHeaderParts.js +1 -0
- package/dist/parsers/parseBoolean.d.ts +1 -1
- package/dist/parsers/parseBoolean.js +2 -1
- package/dist/parsers/parseNumber.d.ts +8 -3
- package/dist/parsers/parseNumber.js +21 -7
- package/dist/parsers/parseSectionHeader.d.ts +3 -2
- package/dist/parsers/parseSectionHeader.js +1 -0
- package/dist/utils/number.d.ts +3 -0
- package/dist/utils/number.js +18 -0
- package/dist/utils/object.d.ts +55 -0
- package/dist/utils/object.js +85 -0
- package/dist/utils/string.d.ts +21 -1
- package/dist/utils/string.js +39 -4
- package/dist/utils/system.d.ts +15 -0
- package/dist/utils/system.js +21 -0
- package/dist/yiniHelpers.d.ts +3 -0
- package/dist/yiniHelpers.js +43 -7
- package/package.json +1 -1
- package/dist/core/YINIVisitor.d.ts +0 -158
- package/dist/core/YINIVisitor.js +0 -1008
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## --dev/uppcoming--
|
|
4
|
+
|
|
5
|
+
## 1.1.0-beta - 2025 Sep
|
|
6
|
+
### Parser
|
|
7
|
+
- **Reimplemented parser from scratch** (`core/ASTBuilder.ts`) using the refactored grammar for a much cleaner and more maintainable design.
|
|
8
|
+
- **Build logic reimplemented** (`core/objectBuilder.ts`) for improved reliability and consistency.
|
|
9
|
+
- **Error reporting enhanced** to be more user-friendly and informative.
|
|
10
|
+
- **Fixed bug sometimes wrong line** Sometimes incorrect line number in error messages are now reported accurately.
|
|
11
|
+
- Renamed option `bailSensitivity` to a more shorter and user-friendly name `failLevel`.
|
|
12
|
+
- **Extended `parse` and `parseFile` methods** to support an options-object form.
|
|
13
|
+
Example:
|
|
14
|
+
```ts
|
|
15
|
+
const config= YINI.parse(yini, {
|
|
16
|
+
strictMode: false,
|
|
17
|
+
failLevel: 'auto',
|
|
18
|
+
includeMetaData: false,
|
|
19
|
+
requireDocTerminator: false,
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
### Specification Alignment
|
|
23
|
+
- Updated to follow the **latest YINI specification (v1.0.0-rc.3)**.
|
|
24
|
+
- Document terminator `/END` is now optional in both lenient and strict mode.
|
|
25
|
+
### Meta Data
|
|
26
|
+
- The optional returned **meta data structure** has been redesigned.
|
|
27
|
+
- Now includes improved organization.
|
|
28
|
+
- **Timing support** added for detailed performance insight.
|
|
29
|
+
- Grouping with **source**, **structure**, **diagnostics**, etc.
|
|
30
|
+
|
|
3
31
|
## 1.0.2-beta - 2025 Aug
|
|
4
32
|
- Fixed issues with floats, including negative and exponential numbers with new test files here: `tests/fixed-issues/issue-32/*`
|
|
5
33
|
- Fixed an issue where parseFile(..) did not output a warning when parsing a file missing a newline at EOF. Plus added test cases to check that it is fixed (in `tests/fixed-issues/issue-30`).
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ YINI (Yet another INI): YINI is a configuration format designed for readability
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
## YINI Parser – TypeScript
|
|
24
|
+
## YINI Parser – (source code in TypeScript)
|
|
25
25
|
|
|
26
26
|
> 🚧 This package is in **beta**. The core API is stabilizing, some more advanced features may still change. Feedback and bug reports are welcome!
|
|
27
27
|
|
|
@@ -58,20 +58,25 @@ YINI is a simple, human-friendly configuration format inspired by INI and JSON.
|
|
|
58
58
|
|
|
59
59
|
## Quick Start
|
|
60
60
|
|
|
61
|
-
A
|
|
61
|
+
A small example using YINI in TypeScript/JavaScript:
|
|
62
62
|
```ts
|
|
63
63
|
import YINI from 'yini-parser'
|
|
64
64
|
|
|
65
65
|
const config = YINI.parse(`
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
// YINI is a simple, human-readable configuration file format.
|
|
67
|
+
|
|
68
|
+
// Note: In YINI, spaces and tabs don't change meaning -
|
|
69
|
+
// indentation is just for readability.
|
|
70
|
+
|
|
71
|
+
^ App // Definition of section (group) "App"
|
|
72
|
+
name = 'My Title' // Keys and values are written as key = value
|
|
73
|
+
items = 25
|
|
74
|
+
darkMode = true // "ON" and "YES" works too
|
|
75
|
+
|
|
76
|
+
// Sub-section of the "App" section
|
|
77
|
+
^^ Special
|
|
78
|
+
primaryColor = #336699 // Hex number format
|
|
79
|
+
isCaching = false // "OFF" and "NO" works too
|
|
75
80
|
`)
|
|
76
81
|
|
|
77
82
|
// To parse from a file instead:
|
|
@@ -89,15 +94,15 @@ My Title
|
|
|
89
94
|
false
|
|
90
95
|
|
|
91
96
|
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
App: {
|
|
98
|
+
name: 'My Title',
|
|
99
|
+
items: 25,
|
|
100
|
+
darkMode: true,
|
|
101
|
+
Special: {
|
|
102
|
+
primaryColor: 3368601,
|
|
103
|
+
isCaching: false
|
|
104
|
+
}
|
|
99
105
|
}
|
|
100
|
-
}
|
|
101
106
|
}
|
|
102
107
|
```
|
|
103
108
|
|
|
@@ -229,45 +234,44 @@ These examples are also included in the npm package.
|
|
|
229
234
|
|
|
230
235
|
---
|
|
231
236
|
|
|
232
|
-
##
|
|
237
|
+
## Bigger Example
|
|
233
238
|
|
|
234
239
|
```js
|
|
235
240
|
const YINI = require('yini-parser').default; // Or: import YINI from 'yini-parser';
|
|
236
241
|
|
|
237
242
|
const config = YINI.parse(`
|
|
238
|
-
|
|
239
|
-
|
|
243
|
+
// This is a comment in YINI
|
|
244
|
+
// YINI is a simple, human-readable configuration file format.
|
|
245
|
+
|
|
246
|
+
// Note: In YINI, spaces and tabs don't change meaning - indentation is just
|
|
247
|
+
// for readability.
|
|
248
|
+
|
|
249
|
+
/* This is a block comment
|
|
250
|
+
|
|
251
|
+
In YINI, section headers use repeated characters "^" at the start to
|
|
252
|
+
show their level: (Section header names are case-sensitive.)
|
|
253
|
+
|
|
254
|
+
^ SectionLevel1
|
|
255
|
+
^^ SectionLevel2
|
|
256
|
+
^^^ SectionLevel3
|
|
240
257
|
*/
|
|
241
258
|
|
|
242
|
-
|
|
259
|
+
^ App // Definition of section (group) "App"
|
|
260
|
+
title = 'My App'
|
|
261
|
+
items = 25
|
|
262
|
+
debug = ON // "true" and "YES" works too
|
|
243
263
|
|
|
244
|
-
^
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
^^
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
user = "appuser"
|
|
254
|
-
--password = "dbpassword" # Disabled line due to --.
|
|
255
|
-
//password = "dbpassword" # Not sure yet about this pw.
|
|
256
|
-
password = "dbpassword" # Keep this secret.
|
|
257
|
-
|
|
258
|
-
// Commenting with slashes works too.
|
|
259
|
-
^^^ Pool
|
|
260
|
-
min = 2
|
|
261
|
-
max = 10
|
|
262
|
-
idleTimeout = 300
|
|
263
|
-
|
|
264
|
-
/* Block comment on a single line. */
|
|
265
|
-
^^ Logging
|
|
266
|
-
level = "info"
|
|
267
|
-
logToFile = ON # This is a comment.
|
|
268
|
-
filePath = "./logs/app.log"
|
|
264
|
+
^ Server // Definition of section (group) "Server"
|
|
265
|
+
host = 'localhost'
|
|
266
|
+
port = 8080
|
|
267
|
+
useTLS = OFF // "false" and "NO" works too
|
|
268
|
+
|
|
269
|
+
// Sub-section of "Server"
|
|
270
|
+
^^ Login
|
|
271
|
+
username = 'user_name'
|
|
272
|
+
password = 'your_password_here'
|
|
269
273
|
|
|
270
|
-
/END
|
|
274
|
+
/END // (only optional)
|
|
271
275
|
`);
|
|
272
276
|
|
|
273
277
|
console.log(config);
|
|
@@ -278,60 +282,41 @@ console.log(config);
|
|
|
278
282
|
// JS object
|
|
279
283
|
{
|
|
280
284
|
App: {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
debug:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
idleTimeout: 300
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
Logging: {
|
|
296
|
-
level: "info",
|
|
297
|
-
logToFile: true,
|
|
298
|
-
filePath: "./logs/app.log"
|
|
285
|
+
title: 'My App',
|
|
286
|
+
items: 25,
|
|
287
|
+
debug: true
|
|
288
|
+
},
|
|
289
|
+
Server: {
|
|
290
|
+
host: 'localhost',
|
|
291
|
+
port: 8080,
|
|
292
|
+
useTLS: false,
|
|
293
|
+
Login: {
|
|
294
|
+
username: 'user_name',
|
|
295
|
+
password: 'your_password_here'
|
|
299
296
|
}
|
|
300
297
|
}
|
|
301
298
|
}
|
|
302
299
|
```
|
|
303
300
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
- 1 = 'Abort-on-Errors'
|
|
324
|
-
- 2 = 'Abort-Even-on-Warnings'
|
|
325
|
-
|
|
326
|
-
Returns a JavaScript object representing the parsed configuration (YINI content).
|
|
327
|
-
|
|
328
|
-
### `YINI.parseFile(filePath: string, strictMode?: boolean, bailSensitivity: 'auto' | 0 | 1 | 2 = "auto", includeMetaData = false): object`
|
|
329
|
-
|
|
330
|
-
Parses YINI code from a file.
|
|
331
|
-
- `filePath`: Path to the `.yini` file.
|
|
332
|
-
- Other parameters are the same as `parse(..)`.
|
|
333
|
-
|
|
334
|
-
Returns a JavaScript object representing the parsed YINI configuration file.
|
|
301
|
+
### Output in JSON:
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"App": {
|
|
305
|
+
"title": "My App",
|
|
306
|
+
"items": 25,
|
|
307
|
+
"debug": true
|
|
308
|
+
},
|
|
309
|
+
"Server": {
|
|
310
|
+
"host": "localhost",
|
|
311
|
+
"port": 8080,
|
|
312
|
+
"useTLS": false,
|
|
313
|
+
"Login": {
|
|
314
|
+
"username": "user_name",
|
|
315
|
+
"password": "your_password_here"
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
335
320
|
|
|
336
321
|
---
|
|
337
322
|
|
package/dist/YINI.d.ts
CHANGED
|
@@ -1,43 +1,66 @@
|
|
|
1
|
-
import { TJSObject } from './core/types';
|
|
1
|
+
import { IAllUserOptions, TJSObject, TPreferredFailLevel } from './core/types';
|
|
2
2
|
/**
|
|
3
3
|
* This class is the public API, which exposes only parse(..) and
|
|
4
4
|
* parseFile(..), rest of the implementation details are hidden.
|
|
5
5
|
* @note Only parse and parseFile are public.
|
|
6
6
|
*/
|
|
7
7
|
export default class YINI {
|
|
8
|
-
static
|
|
8
|
+
private static g_tabSize;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* @returns The number of spaces per tab character used in error messages.
|
|
11
|
+
*/
|
|
12
|
+
static getTabSize(): number;
|
|
13
|
+
/**
|
|
14
|
+
* Overrides the number of spaces per tab character used in error messages.
|
|
15
|
+
* Allowed range: 1-32.
|
|
16
|
+
*/
|
|
17
|
+
static setTabSize(spaces: number): void;
|
|
18
|
+
/**
|
|
19
|
+
* Parse inline YINI content into a JavaScript object.
|
|
11
20
|
*
|
|
12
21
|
* @param yiniContent YINI code as a string (multi‑line content supported).
|
|
13
22
|
* @param strictMode If `true`, enforce strict parsing rules (e.g. require `/END`, disallow trailing commas).
|
|
14
|
-
* @param
|
|
15
|
-
* - `'auto'`
|
|
23
|
+
* @param failLevel Preferred bail sensitivity level, controls how errors and warnings are handled:
|
|
24
|
+
* - `'auto'` (default) : Auto‑select level (strict→1, lenient→0)
|
|
16
25
|
* - `0` / `'Ignore-Errors'` : Continue parsing despite errors; log them and attempt recovery.
|
|
17
26
|
* - `1` / `'Abort-on-Errors'` : Stop parsing on the first error.
|
|
18
27
|
* - `2` / `'Abort-Even-on-Warnings'`: Stop parsing on the first warning **or** error.
|
|
19
28
|
* @param includeMetaData If `true`, return additional metadata (e.g. warnings, statistics) alongside the parsed object.
|
|
20
29
|
*
|
|
21
|
-
* @
|
|
30
|
+
* @returns A JavaScript object representing the parsed YINI content.
|
|
31
|
+
*/
|
|
32
|
+
static parse(yiniContent: string, strictMode?: boolean, failLevel?: TPreferredFailLevel, includeMetaData?: boolean): TJSObject;
|
|
33
|
+
/**
|
|
34
|
+
* Parse inline YINI content into a JavaScript object, using an options object for configuration.
|
|
35
|
+
*
|
|
36
|
+
* @param yiniContent YINI code as a string (multi‑line content supported).
|
|
37
|
+
* @param options Optional settings to customize parsing and/or results, useful if you need more control.
|
|
22
38
|
*
|
|
23
39
|
* @returns A JavaScript object representing the parsed YINI content.
|
|
24
40
|
*/
|
|
25
|
-
static parse
|
|
41
|
+
static parse(yiniContent: string, options?: IAllUserOptions): TJSObject;
|
|
26
42
|
/**
|
|
27
43
|
* Parse a YINI file into a JavaScript object.
|
|
28
44
|
*
|
|
29
45
|
* @param yiniFile Path to the YINI file.
|
|
30
46
|
* @param strictMode If `true`, enforce strict parsing rules (e.g. require `/END`, disallow trailing commas).
|
|
31
|
-
* @param
|
|
32
|
-
* - `'auto'`
|
|
47
|
+
* @param failLevel Preferred bail sensitivity level, controls how errors and warnings are handled:
|
|
48
|
+
* - `'auto'` (default) : Auto‑select level (strict→1, lenient→0)
|
|
33
49
|
* - `0` / `'Ignore-Errors'` : Continue parsing despite errors; log them and attempt recovery.
|
|
34
50
|
* - `1` / `'Abort-on-Errors'` : Stop parsing on the first error.
|
|
35
51
|
* - `2` / `'Abort-Even-on-Warnings'`: Stop parsing on the first warning **or** error.
|
|
36
52
|
* @param includeMetaData If `true`, return additional metadata (e.g. warnings, statistics) alongside the parsed object.
|
|
37
53
|
*
|
|
38
|
-
* @
|
|
54
|
+
* @returns A JavaScript object representing the parsed YINI content.
|
|
55
|
+
*/
|
|
56
|
+
static parseFile(filePath: string, strictMode?: boolean, failLevel?: TPreferredFailLevel, includeMetaData?: boolean): TJSObject;
|
|
57
|
+
/**
|
|
58
|
+
* Parse a YINI file into a JavaScript object, using an options object for configuration.
|
|
59
|
+
*
|
|
60
|
+
* @param yiniFile Path to the YINI file.
|
|
61
|
+
* @param options Optional settings to customize parsing and/or results, useful if you need more control.
|
|
39
62
|
*
|
|
40
63
|
* @returns A JavaScript object representing the parsed YINI content.
|
|
41
64
|
*/
|
|
42
|
-
static parseFile
|
|
65
|
+
static parseFile(filePath: string, options?: IAllUserOptions): TJSObject;
|
|
43
66
|
}
|
package/dist/YINI.js
CHANGED
|
@@ -2,113 +2,226 @@
|
|
|
2
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
|
-
var _a;
|
|
6
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
6
|
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
8
8
|
const env_1 = require("./config/env");
|
|
9
|
+
const ErrorDataHandler_1 = require("./core/ErrorDataHandler");
|
|
9
10
|
const parseEntry_1 = require("./parseEntry");
|
|
10
11
|
const pathAndFileName_1 = require("./utils/pathAndFileName");
|
|
11
12
|
const print_1 = require("./utils/print");
|
|
13
|
+
const string_1 = require("./utils/string");
|
|
14
|
+
const DEFAULT_TAB_SIZE = 4; // De facto "modern default" (even though traditionally/historically it's 8).
|
|
15
|
+
let _runtimeInfo = {
|
|
16
|
+
sourceType: 'Inline',
|
|
17
|
+
fileName: undefined,
|
|
18
|
+
fileByteSize: null,
|
|
19
|
+
lineCount: null,
|
|
20
|
+
timeIoMs: null,
|
|
21
|
+
preferredBailSensitivity: null,
|
|
22
|
+
sha256: null,
|
|
23
|
+
};
|
|
24
|
+
// Type guard: did the caller use the options-object form?
|
|
25
|
+
const isOptionsObjectForm = (v) => {
|
|
26
|
+
return (v != null &&
|
|
27
|
+
typeof v === 'object' &&
|
|
28
|
+
// Note: If one wants, this can be relax to "typeof v === 'object'"
|
|
29
|
+
// but this keeps accidental booleans/strings out.
|
|
30
|
+
('strictMode' in v ||
|
|
31
|
+
'failLevel' in v ||
|
|
32
|
+
'includeMetaData' in v ||
|
|
33
|
+
'includeDiagnostics' in v ||
|
|
34
|
+
'includeTiming' in v ||
|
|
35
|
+
'preserveUndefinedInMeta' in v ||
|
|
36
|
+
'requireDocTerminator' in v));
|
|
37
|
+
};
|
|
38
|
+
// Initial default values.
|
|
39
|
+
const DEFAULT_OPTS = {
|
|
40
|
+
strictMode: false,
|
|
41
|
+
failLevel: 'auto',
|
|
42
|
+
includeMetaData: false,
|
|
43
|
+
includeDiagnostics: false,
|
|
44
|
+
includeTiming: false,
|
|
45
|
+
preserveUndefinedInMeta: false,
|
|
46
|
+
requireDocTerminator: false,
|
|
47
|
+
};
|
|
12
48
|
/**
|
|
13
49
|
* This class is the public API, which exposes only parse(..) and
|
|
14
50
|
* parseFile(..), rest of the implementation details are hidden.
|
|
15
51
|
* @note Only parse and parseFile are public.
|
|
16
52
|
*/
|
|
17
53
|
class YINI {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
* @param yiniContent YINI code as a string (multi‑line content supported).
|
|
25
|
-
* @param strictMode If `true`, enforce strict parsing rules (e.g. require `/END`, disallow trailing commas).
|
|
26
|
-
* @param bailSensitivity Controls how errors and warnings are handled:
|
|
27
|
-
* - `'auto'` : Auto‑select level (strict→1, lenient→0)
|
|
28
|
-
* - `0` / `'Ignore-Errors'` : Continue parsing despite errors; log them and attempt recovery.
|
|
29
|
-
* - `1` / `'Abort-on-Errors'` : Stop parsing on the first error.
|
|
30
|
-
* - `2` / `'Abort-Even-on-Warnings'`: Stop parsing on the first warning **or** error.
|
|
31
|
-
* @param includeMetaData If `true`, return additional metadata (e.g. warnings, statistics) alongside the parsed object.
|
|
32
|
-
*
|
|
33
|
-
* @note The order of properties in each output object may differ from their order in the YINI source.
|
|
34
|
-
*
|
|
35
|
-
* @returns A JavaScript object representing the parsed YINI content.
|
|
36
|
-
*/
|
|
37
|
-
YINI.parse = (yiniContent, strictMode = false, bailSensitivity = 'auto', includeMetaData = false) => {
|
|
38
|
-
(0, print_1.debugPrint)('-> Entered static parse(..) in class YINI\n');
|
|
39
|
-
// Important: First, before anything, trim beginning and trailing whitespaces!
|
|
40
|
-
yiniContent = yiniContent.trim();
|
|
41
|
-
if (!yiniContent) {
|
|
42
|
-
throw new Error('Syntax-Error: Unexpected blank YINI input');
|
|
54
|
+
/**
|
|
55
|
+
* @returns The number of spaces per tab character used in error messages.
|
|
56
|
+
*/
|
|
57
|
+
static getTabSize() {
|
|
58
|
+
return this.g_tabSize;
|
|
43
59
|
}
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Overrides the number of spaces per tab character used in error messages.
|
|
62
|
+
* Allowed range: 1-32.
|
|
63
|
+
*/
|
|
64
|
+
static setTabSize(spaces) {
|
|
65
|
+
if (spaces < 1 || spaces > 32) {
|
|
66
|
+
new ErrorDataHandler_1.ErrorDataHandler('None/Ignore').pushOrBail(null, 'Fatal-Error', `Invalid tab size ${spaces} is out of range.`, 'Tab size must be between 1 and 32 spaces.');
|
|
67
|
+
}
|
|
68
|
+
this.g_tabSize = spaces;
|
|
46
69
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
70
|
+
// --- Single implementation --------------------------------------------
|
|
71
|
+
// Implementation method (not declared with arrow function) for both method overload signatures.
|
|
72
|
+
// NOTE: Must be method declaration with NO =, arrow functions not (currently) supported for this type of method overloading.
|
|
73
|
+
static parse(yiniContent, arg2, // strictMode | options
|
|
74
|
+
failLevel = DEFAULT_OPTS.failLevel, includeMetaData = DEFAULT_OPTS.includeMetaData) {
|
|
75
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
76
|
+
(0, print_1.debugPrint)('-> Entered static parse(..) in class YINI\n');
|
|
77
|
+
// Runtime guard to catch illegal/ambiguous calls coming from JS or any-cast code
|
|
78
|
+
if (isOptionsObjectForm(arg2) &&
|
|
79
|
+
(failLevel !== 'auto' || includeMetaData !== false)) {
|
|
80
|
+
throw new TypeError('Invalid call: when providing an options object, do not also pass positional parameters.');
|
|
81
|
+
}
|
|
82
|
+
// Normalize to a fully-required options object.
|
|
83
|
+
let userOpts;
|
|
84
|
+
// Required, makes all properties in T required, no undefined.
|
|
85
|
+
// const userOpts: Required<IAllUserOptions> = isOptionsObjectForm(arg2)
|
|
86
|
+
if (isOptionsObjectForm(arg2)) {
|
|
87
|
+
// Options-object Form.
|
|
88
|
+
/* parse = (
|
|
89
|
+
yiniContent: string,
|
|
90
|
+
options: IAllUserOptions,
|
|
91
|
+
)
|
|
92
|
+
*/
|
|
93
|
+
userOpts = Object.assign(Object.assign({}, DEFAULT_OPTS), { strictMode: (_a = arg2.strictMode) !== null && _a !== void 0 ? _a : DEFAULT_OPTS.strictMode, failLevel: (_b = arg2.failLevel) !== null && _b !== void 0 ? _b : DEFAULT_OPTS.failLevel, includeMetaData: (_c = arg2.includeMetaData) !== null && _c !== void 0 ? _c : DEFAULT_OPTS.includeMetaData, includeDiagnostics: (_d = arg2.includeDiagnostics) !== null && _d !== void 0 ? _d : DEFAULT_OPTS.includeDiagnostics, includeTiming: (_e = arg2.includeTiming) !== null && _e !== void 0 ? _e : DEFAULT_OPTS.includeTiming, preserveUndefinedInMeta: (_f = arg2.preserveUndefinedInMeta) !== null && _f !== void 0 ? _f : DEFAULT_OPTS.preserveUndefinedInMeta, requireDocTerminator: (_g = arg2.requireDocTerminator) !== null && _g !== void 0 ? _g : DEFAULT_OPTS.requireDocTerminator });
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Positional form.
|
|
97
|
+
/* parse = (
|
|
98
|
+
yiniContent: string,
|
|
99
|
+
strictMode?: boolean,
|
|
100
|
+
failLevel?: TPreferredFailLevel,
|
|
101
|
+
includeMetaData?: boolean,
|
|
102
|
+
)
|
|
103
|
+
*/
|
|
104
|
+
userOpts = Object.assign(Object.assign({}, DEFAULT_OPTS), { strictMode: (_h = arg2) !== null && _h !== void 0 ? _h : DEFAULT_OPTS.strictMode, failLevel,
|
|
105
|
+
includeMetaData });
|
|
106
|
+
}
|
|
107
|
+
if (userOpts.includeMetaData && _runtimeInfo.sourceType === 'Inline') {
|
|
108
|
+
const lineCount = yiniContent.split(/\r?\n/).length; // Counts the lines.
|
|
109
|
+
const sha256 = (0, string_1.computeSha256)(yiniContent); // NOTE: Compute BEFORE any possible tampering of content.
|
|
110
|
+
_runtimeInfo.lineCount = lineCount;
|
|
111
|
+
_runtimeInfo.preferredBailSensitivity = userOpts.failLevel;
|
|
112
|
+
_runtimeInfo.sha256 = sha256;
|
|
113
|
+
}
|
|
114
|
+
// NOTE: Important: Do not trim or mutate the yiniContent here, due
|
|
115
|
+
// to it will mess up the line numbers in error reporting.
|
|
116
|
+
if (!yiniContent) {
|
|
117
|
+
throw new Error('Syntax-Error: Unexpected blank YINI input');
|
|
118
|
+
}
|
|
119
|
+
if (!yiniContent.endsWith('\n')) {
|
|
120
|
+
yiniContent += '\n';
|
|
121
|
+
}
|
|
122
|
+
let level = 0;
|
|
123
|
+
if (userOpts.failLevel === 'auto') {
|
|
124
|
+
if (!userOpts.strictMode)
|
|
125
|
+
level = 0;
|
|
126
|
+
if (userOpts.strictMode)
|
|
127
|
+
level = 1;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
level = userOpts.failLevel;
|
|
131
|
+
}
|
|
132
|
+
const coreOpts = {
|
|
133
|
+
isStrict: userOpts.strictMode,
|
|
134
|
+
bailSensitivity: level,
|
|
135
|
+
isIncludeMeta: userOpts.includeMetaData,
|
|
136
|
+
isWithDiagnostics: (0, env_1.isDev)() || (0, env_1.isDebug)() || userOpts.includeDiagnostics,
|
|
137
|
+
isWithTiming: (0, env_1.isDev)() || (0, env_1.isDebug)() || userOpts.includeTiming,
|
|
138
|
+
isKeepUndefinedInMeta: (0, env_1.isDebug)() || userOpts.preserveUndefinedInMeta,
|
|
139
|
+
isRequireDocTerminator: userOpts.requireDocTerminator,
|
|
140
|
+
};
|
|
141
|
+
(0, print_1.debugPrint)();
|
|
142
|
+
(0, print_1.debugPrint)('==== Call parse ==========================');
|
|
143
|
+
const result = (0, parseEntry_1._parseMain)(yiniContent, coreOpts, _runtimeInfo);
|
|
144
|
+
(0, print_1.debugPrint)('==== End call parse ==========================\n');
|
|
145
|
+
if ((0, env_1.isDev)()) {
|
|
146
|
+
console.log();
|
|
147
|
+
(0, print_1.devPrint)('YINI.parse(..): result:');
|
|
148
|
+
console.log(result);
|
|
149
|
+
(0, print_1.devPrint)('Complete result:');
|
|
150
|
+
(0, print_1.printObject)(result);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
53
153
|
}
|
|
54
|
-
|
|
55
|
-
|
|
154
|
+
// --- Single implementation --------------------------------------------
|
|
155
|
+
// Implementation method (not declared with arrow function) for both method overload signatures.
|
|
156
|
+
// NOTE: Must be method declaration with NO =, arrow functions not (currently) supported for this type of method overloading.
|
|
157
|
+
static parseFile(filePath, arg2, // strictMode | options
|
|
158
|
+
failLevel = DEFAULT_OPTS.failLevel, includeMetaData = DEFAULT_OPTS.includeMetaData) {
|
|
159
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
160
|
+
(0, print_1.debugPrint)('-> Entered static parseFile(..) in class YINI\n');
|
|
161
|
+
(0, print_1.debugPrint)('Current directory = ' + process.cwd());
|
|
162
|
+
// Runtime guard to catch illegal/ambiguous calls coming from JS or any-cast code
|
|
163
|
+
if (isOptionsObjectForm(arg2) &&
|
|
164
|
+
(failLevel !== 'auto' || includeMetaData !== false)) {
|
|
165
|
+
throw new TypeError('Invalid call: when providing an options object, do not also pass positional parameters.');
|
|
166
|
+
}
|
|
167
|
+
// Normalize to a fully-required options object.
|
|
168
|
+
let userOpts;
|
|
169
|
+
// Required, makes all properties in T required, no undefined.
|
|
170
|
+
// const userOpts: Required<IAllUserOptions> = isOptionsObjectForm(arg2)
|
|
171
|
+
if (isOptionsObjectForm(arg2)) {
|
|
172
|
+
// Options-object Form.
|
|
173
|
+
/* parse = (
|
|
174
|
+
yiniContent: string,
|
|
175
|
+
options: IAllUserOptions,
|
|
176
|
+
)
|
|
177
|
+
*/
|
|
178
|
+
userOpts = Object.assign(Object.assign({}, DEFAULT_OPTS), { strictMode: (_a = arg2.strictMode) !== null && _a !== void 0 ? _a : DEFAULT_OPTS.strictMode, failLevel: (_b = arg2.failLevel) !== null && _b !== void 0 ? _b : DEFAULT_OPTS.failLevel, includeMetaData: (_c = arg2.includeMetaData) !== null && _c !== void 0 ? _c : DEFAULT_OPTS.includeMetaData, includeDiagnostics: (_d = arg2.includeDiagnostics) !== null && _d !== void 0 ? _d : DEFAULT_OPTS.includeDiagnostics, includeTiming: (_e = arg2.includeTiming) !== null && _e !== void 0 ? _e : DEFAULT_OPTS.includeTiming, preserveUndefinedInMeta: (_f = arg2.preserveUndefinedInMeta) !== null && _f !== void 0 ? _f : DEFAULT_OPTS.preserveUndefinedInMeta, requireDocTerminator: (_g = arg2.requireDocTerminator) !== null && _g !== void 0 ? _g : DEFAULT_OPTS.requireDocTerminator });
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// Positional form.
|
|
182
|
+
/* parse = (
|
|
183
|
+
yiniContent: string,
|
|
184
|
+
strictMode?: boolean,
|
|
185
|
+
failLevel?: TPreferredFailLevel,
|
|
186
|
+
includeMetaData?: boolean,
|
|
187
|
+
)
|
|
188
|
+
*/
|
|
189
|
+
userOpts = Object.assign(Object.assign({}, DEFAULT_OPTS), { strictMode: (_h = arg2) !== null && _h !== void 0 ? _h : DEFAULT_OPTS.strictMode, failLevel,
|
|
190
|
+
includeMetaData });
|
|
191
|
+
}
|
|
192
|
+
if ((0, pathAndFileName_1.getFileNameExtension)(filePath).toLowerCase() !== '.yini') {
|
|
193
|
+
console.error('Invalid file extension for YINI file:');
|
|
194
|
+
console.error(`"${filePath}"`);
|
|
195
|
+
console.log('File does not have a valid ".yini" extension (case-insensitive).');
|
|
196
|
+
throw new Error('Error: Unexpected file extension for YINI file');
|
|
197
|
+
}
|
|
198
|
+
// ---- Phase 0: I/O ----
|
|
199
|
+
const timeStartMs = perf_hooks_1.performance.now();
|
|
200
|
+
// let content = fs.readFileSync(filePath, 'utf8')
|
|
201
|
+
const rawBuffer = fs_1.default.readFileSync(filePath); // Raw buffer for size.
|
|
202
|
+
const fileByteSize = rawBuffer.byteLength; // Byte size in UTF-8.
|
|
203
|
+
let content = rawBuffer.toString('utf8');
|
|
204
|
+
const timeEndMs = perf_hooks_1.performance.now();
|
|
205
|
+
_runtimeInfo.sourceType = 'File';
|
|
206
|
+
_runtimeInfo.fileName = filePath;
|
|
207
|
+
if (userOpts.includeMetaData) {
|
|
208
|
+
_runtimeInfo.lineCount = content.split(/\r?\n/).length; // Counts the lines.
|
|
209
|
+
_runtimeInfo.fileByteSize = fileByteSize;
|
|
210
|
+
_runtimeInfo.timeIoMs = +(timeEndMs - timeStartMs);
|
|
211
|
+
_runtimeInfo.preferredBailSensitivity = userOpts.failLevel;
|
|
212
|
+
_runtimeInfo.sha256 = (0, string_1.computeSha256)(content); // NOTE: Compute BEFORE any possible tampering of content.
|
|
213
|
+
}
|
|
214
|
+
let hasNoNewlineAtEOF = false;
|
|
215
|
+
if (!content.endsWith('\n')) {
|
|
216
|
+
content += '\n';
|
|
217
|
+
hasNoNewlineAtEOF = true;
|
|
218
|
+
}
|
|
219
|
+
const result = this.parse(content, userOpts.strictMode, userOpts.failLevel, userOpts.includeMetaData);
|
|
220
|
+
if (hasNoNewlineAtEOF) {
|
|
221
|
+
console.warn(`No newline at end of file, it's recommended to end a file with a newline. File:\n"${filePath}"`);
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
56
224
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
bailSensitivityLevel: level,
|
|
60
|
-
isIncludeMeta: includeMetaData,
|
|
61
|
-
isWithDiagnostics: (0, env_1.isDev)() || (0, env_1.isDebug)(),
|
|
62
|
-
isWithTiming: (0, env_1.isDebug)(),
|
|
63
|
-
};
|
|
64
|
-
(0, print_1.debugPrint)();
|
|
65
|
-
(0, print_1.debugPrint)('==== Call parse ==========================');
|
|
66
|
-
const result = (0, parseEntry_1.parseMain)(yiniContent, options);
|
|
67
|
-
(0, print_1.debugPrint)('==== End call parse ==========================\n');
|
|
68
|
-
if ((0, env_1.isDev)()) {
|
|
69
|
-
console.log();
|
|
70
|
-
(0, print_1.devPrint)('YINI.parse(..): result:');
|
|
71
|
-
console.log(result);
|
|
72
|
-
(0, print_1.devPrint)('Complete result:');
|
|
73
|
-
(0, print_1.printObject)(result);
|
|
74
|
-
}
|
|
75
|
-
return result;
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Parse a YINI file into a JavaScript object.
|
|
79
|
-
*
|
|
80
|
-
* @param yiniFile Path to the YINI file.
|
|
81
|
-
* @param strictMode If `true`, enforce strict parsing rules (e.g. require `/END`, disallow trailing commas).
|
|
82
|
-
* @param bailSensitivity Controls how errors and warnings are handled:
|
|
83
|
-
* - `'auto'` : Auto‑select level (strict→1, lenient→0)
|
|
84
|
-
* - `0` / `'Ignore-Errors'` : Continue parsing despite errors; log them and attempt recovery.
|
|
85
|
-
* - `1` / `'Abort-on-Errors'` : Stop parsing on the first error.
|
|
86
|
-
* - `2` / `'Abort-Even-on-Warnings'`: Stop parsing on the first warning **or** error.
|
|
87
|
-
* @param includeMetaData If `true`, return additional metadata (e.g. warnings, statistics) alongside the parsed object.
|
|
88
|
-
*
|
|
89
|
-
* @note The order of properties in each output object may differ from their order in the YINI source.
|
|
90
|
-
*
|
|
91
|
-
* @returns A JavaScript object representing the parsed YINI content.
|
|
92
|
-
*/
|
|
93
|
-
YINI.parseFile = (filePath, strictMode = false, bailSensitivity = 'auto', includeMetaData = false) => {
|
|
94
|
-
(0, print_1.debugPrint)('Current directory = ' + process.cwd());
|
|
95
|
-
if ((0, pathAndFileName_1.getFileNameExtension)(filePath).toLowerCase() !== '.yini') {
|
|
96
|
-
console.error('Invalid file extension for YINI file:');
|
|
97
|
-
console.error(`"${filePath}"`);
|
|
98
|
-
console.log('File does not have a valid ".yini" extension (case-insensitive).');
|
|
99
|
-
throw new Error('Error: Unexpected file extension for YINI file');
|
|
100
|
-
}
|
|
101
|
-
let content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
102
|
-
let hasNoNewlineAtEOF = false;
|
|
103
|
-
if (!content.endsWith('\n')) {
|
|
104
|
-
content += '\n';
|
|
105
|
-
hasNoNewlineAtEOF = true;
|
|
106
|
-
}
|
|
107
|
-
_a.filePath = filePath;
|
|
108
|
-
const result = _a.parse(content, strictMode, bailSensitivity, includeMetaData);
|
|
109
|
-
if (hasNoNewlineAtEOF) {
|
|
110
|
-
console.warn(`No newline at end of file, it's recommended to end a file with a newline. File:\n"${filePath}"`);
|
|
111
|
-
}
|
|
112
|
-
return result;
|
|
113
|
-
};
|
|
225
|
+
}
|
|
226
|
+
YINI.g_tabSize = DEFAULT_TAB_SIZE; // Global tab size used in error messages.
|
|
114
227
|
exports.default = YINI;
|