yini-parser 1.0.0-alpha.1

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +0 -0
  2. package/LICENSE +201 -0
  3. package/README.md +120 -0
  4. package/dist/src/YINI.js +122 -0
  5. package/dist/src/config/env.js +15 -0
  6. package/dist/src/core/ErrorDataHandler.js +207 -0
  7. package/dist/src/core/YINIVisitor.js +856 -0
  8. package/dist/src/core/objectBuilder.js +166 -0
  9. package/dist/src/core/types.js +36 -0
  10. package/dist/src/grammar/YiniLexer.js +370 -0
  11. package/dist/src/grammar/YiniParser.js +2106 -0
  12. package/dist/src/grammar/YiniParserVisitor.js +14 -0
  13. package/dist/src/index.js +189 -0
  14. package/dist/src/parseEntry.js +155 -0
  15. package/dist/src/parsers/extractHeaderParts.js +103 -0
  16. package/dist/src/parsers/extractSignificantYiniLine.js +68 -0
  17. package/dist/src/parsers/parseBoolean.js +12 -0
  18. package/dist/src/parsers/parseNull.js +11 -0
  19. package/dist/src/parsers/parseNumber.js +49 -0
  20. package/dist/src/parsers/parseSectionHeader.js +111 -0
  21. package/dist/src/parsers/parseString.js +40 -0
  22. package/dist/src/utils/pathAndFileName.js +15 -0
  23. package/dist/src/utils/string.js +97 -0
  24. package/dist/src/utils/system.js +23 -0
  25. package/dist/src/yiniHelpers.js +141 -0
  26. package/dist/tests/integration/1-core-parsing/parse-bigger-section-nesting-as-object.test.js +83 -0
  27. package/dist/tests/integration/1-core-parsing/parse-section-nesting-w-classic-markers.test.js +170 -0
  28. package/dist/tests/integration/1-core-parsing/parse-section-nesting-w-nsh-markers.test.js +27 -0
  29. package/dist/tests/integration/1-core-parsing/read some values from level 1 and 2.test.js +77 -0
  30. package/dist/tests/integration/1-core-parsing/throw on bad section heads.test.js +162 -0
  31. package/dist/tests/integration/10-special-validation-modes/validation-modes.test.js +38 -0
  32. package/dist/tests/integration/2-file-structure-and-error/able to parse mixed case filenames.test.js +72 -0
  33. package/dist/tests/integration/2-file-structure-and-error/throw error on bad file extensions.test.js +36 -0
  34. package/dist/tests/integration/2-file-structure-and-error/throw error parsing bad content.test.js +80 -0
  35. package/dist/tests/smoke/A-general-smoke.test.js +259 -0
  36. package/dist/tests/smoke/B-parse-inline-smoke.test.js +270 -0
  37. package/dist/tests/smoke/C-traverse-file-smoke.test.js +141 -0
  38. package/dist/tests/smoke/D-parse-file-smoke.test.js +134 -0
  39. package/dist/tests/unit/parsers/extractHeaderParts.unit.test.js +490 -0
  40. package/dist/tests/unit/parsers/parseSectionHeader-classic.unit.test.js +421 -0
  41. package/dist/tests/unit/parsers/parseSectionHeader-nsh.unit.test.js +436 -0
  42. package/dist/tests/unit/parsers/parseSectionHeader-throw-on-invalid.unit.test.js +168 -0
  43. package/dist/tests/unit/utils/utils-pathAndFileName.unit.test.js +80 -0
  44. package/dist/tests/unit/utils/utils-string.unit.test.js +185 -0
  45. package/dist/tests/unit/yiniHelpers.unit.test.js +306 -0
  46. package/package.json +94 -0
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const extractHeaderParts_1 = __importDefault(require("../parsers/extractHeaderParts"));
7
+ const extractSignificantYiniLine_1 = require("../parsers/extractSignificantYiniLine");
8
+ const string_1 = require("../utils/string");
9
+ const system_1 = require("../utils/system");
10
+ const yiniHelpers_1 = require("../yiniHelpers");
11
+ /**
12
+ * Extract ...
13
+ * @param rawLine Raw line with the section header.
14
+ */
15
+ const parseSectionHeader = (rawLine, errorHandler, ctx) => {
16
+ (0, system_1.debugPrint)('-> Entered parseSectionHeader(..)');
17
+ rawLine = rawLine.trim();
18
+ const line = (0, extractSignificantYiniLine_1.extractYiniLine)(rawLine);
19
+ (0, system_1.debugPrint)(' rawLine: >>>' + rawLine + '<<<');
20
+ (0, system_1.debugPrint)('extractYiniLine(..), line: >>>' + line + '<<<');
21
+ let { strMarkerChars, strSectionName, strNumberPart, isBacktickedName } = (0, extractHeaderParts_1.default)(rawLine, errorHandler, ctx);
22
+ (0, system_1.debugPrint)('In parseSectionHeader(..), after extractHeaderParts(..):');
23
+ (0, system_1.debugPrint)(' strMarkerChars: ' + strMarkerChars);
24
+ (0, system_1.debugPrint)(' strSectionName: ' + strSectionName);
25
+ (0, system_1.debugPrint)(' strNumberPart: ' + strNumberPart);
26
+ (0, system_1.debugPrint)('isBacktickedName: ' + isBacktickedName);
27
+ let headerMarkerType;
28
+ let level = 0;
29
+ if (strMarkerChars === '') {
30
+ errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Unknown section header marker type', 'Section header marker type could not be identified, header text: ' +
31
+ rawLine);
32
+ }
33
+ // --- Determing level and headerMarkerType ------------------------
34
+ if (strNumberPart === '') {
35
+ headerMarkerType = 'Classic-Header-Marker';
36
+ level = strMarkerChars.length;
37
+ if (strNumberPart !== '') {
38
+ errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid extra input in section header of a repeating marker characters: ' +
39
+ strNumberPart, 'Classic section header markers may not include any numbers after, correct header markers looks like ^ Title, ^^ Title, ^^^ Title, etc.');
40
+ }
41
+ }
42
+ else {
43
+ headerMarkerType = 'Numeric-Header-Marker';
44
+ try {
45
+ level = Number.parseInt(strNumberPart);
46
+ }
47
+ catch (err) {
48
+ errorHandler.pushOrBail(ctx, 'Syntax-Error', 'No number in this shorthand section header marker found', 'This shorthand section header marker could not be parse correctly, section header text: ' +
49
+ line +
50
+ ', raw: ' +
51
+ rawLine);
52
+ }
53
+ }
54
+ // ---------------------------------------------------------------
55
+ // --- Check level contraints based on headerMarkerType ----------
56
+ if (headerMarkerType === 'Classic-Header-Marker') {
57
+ if (level > 6) {
58
+ errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid number of repeating marker characters: ' +
59
+ strMarkerChars, 'It is invalid to use seven or more section marker characters in succession (e.g. ^^^^^^^). However, to represent nesting levels deeper than 6, you may switch to the numeric shorthand section header syntax, e.g. ^7.');
60
+ }
61
+ }
62
+ else {
63
+ if (level < 1) {
64
+ errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid number in numeric shorthand section marker: ' +
65
+ strMarkerChars, 'The number in a numeric shorthand section marker must be 1 or higher, e.g. ^1, ^2, ^3, etc.');
66
+ }
67
+ }
68
+ // ---------------------------------------------------------------
69
+ // --- Check naming contraints based on isBacktickedName ----------
70
+ const lenOfName = strSectionName.length;
71
+ if (isBacktickedName) {
72
+ if (!(0, yiniHelpers_1.isValidBacktickedIdent)(strSectionName)) {
73
+ errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid name in this section header, section name: "' +
74
+ strSectionName +
75
+ '"', 'Section name should be backticked like e.g. `My section name`.');
76
+ }
77
+ }
78
+ else {
79
+ (0, system_1.debugPrint)('Naming contraints: Is not a BacktickedName');
80
+ if (lenOfName <= 0) {
81
+ errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid section name in repeating marker characters header, section name: "' +
82
+ strSectionName +
83
+ '"');
84
+ }
85
+ if (!(0, yiniHelpers_1.isValidSimpleIdent)(strSectionName)) {
86
+ errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid name in this section header, section name: "' +
87
+ strSectionName +
88
+ '"', 'Section name must start with: A-Z, a-z, or _, unless enclosed in backticks e.g. `' +
89
+ strSectionName +
90
+ '`, `My section name`.');
91
+ }
92
+ strSectionName = (0, string_1.trimBackticks)(strSectionName);
93
+ }
94
+ // ---------------------------------------------------------------
95
+ // strSectionName = trimBackticks(strSectionName)
96
+ (0, system_1.debugPrint)(' --------------');
97
+ (0, system_1.debugPrint)('<- About to leave parseSectionHeader(..)');
98
+ (0, system_1.debugPrint)(` rawLine = >>>${rawLine}<<<`);
99
+ (0, system_1.debugPrint)(` line = >>>${line}<<<`);
100
+ (0, system_1.debugPrint)();
101
+ (0, system_1.debugPrint)('identified level: ' + level);
102
+ (0, system_1.debugPrint)(' SectionName: ' + strSectionName);
103
+ (0, system_1.debugPrint)('headerMarkerType: ' + headerMarkerType);
104
+ (0, system_1.debugPrint)(' --------------');
105
+ return {
106
+ markerType: headerMarkerType,
107
+ sectionName: strSectionName,
108
+ sectionLevel: level,
109
+ };
110
+ };
111
+ exports.default = parseSectionHeader;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const env_1 = require("../config/env");
4
+ const system_1 = require("../utils/system");
5
+ const parseStringLiteral = (raw) => {
6
+ var _a;
7
+ (0, system_1.debugPrint)('-> Entered parseStringLiteral(..)');
8
+ (0, system_1.debugPrint)('raw = >>>' + raw + '<<<');
9
+ /*
10
+ Extracts an optional prefix (C, c, H, or h) and identifies whether
11
+ the string is triple-quoted, double-quoted, or single-quoted.
12
+ */
13
+ const prefixMatch = raw.match(/^(C|c|H|h|R|r)?("""|"|')/);
14
+ (0, system_1.debugPrint)('prefixMatch:');
15
+ if ((0, env_1.isDebug)()) {
16
+ console.debug(prefixMatch);
17
+ }
18
+ let prefix = prefixMatch ? (_a = prefixMatch[1]) === null || _a === void 0 ? void 0 : _a.toUpperCase() : '';
19
+ (0, system_1.debugPrint)(' prefix = ' + prefix);
20
+ let quoteType = prefixMatch ? prefixMatch[2] : '';
21
+ (0, system_1.debugPrint)(' quoteType = ' + quoteType);
22
+ (0, system_1.debugPrint)('quoteType.length = ' + quoteType.length);
23
+ // Extracts the substring after removing the initial prefix (if any)
24
+ // and quotes at the start (prefix.length + quoteType.length) and the
25
+ // quotes at the end (-quoteType.length).
26
+ let inner = raw.slice(((prefix === null || prefix === void 0 ? void 0 : prefix.length) || 0) + quoteType.length, -quoteType.length);
27
+ (0, system_1.debugPrint)('inner (raw) = ' + inner);
28
+ if (prefix === 'C') {
29
+ inner = inner
30
+ .replace(/\\n/g, '\n')
31
+ .replace(/\\t/g, '\t')
32
+ .replace(/\\r/g, '\r');
33
+ }
34
+ else if (prefix === 'H') {
35
+ inner = inner.replace(/[\s\n\r]+/g, ' ').trim();
36
+ }
37
+ (0, system_1.debugPrint)('inner (reformat) = ' + inner);
38
+ return inner;
39
+ };
40
+ exports.default = parseStringLiteral;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * This file contains general path and file name helper functions (utils).
4
+ * @note More specific YINI helper functions should go into yiniHelpers.ts-file.
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.getFileNameExtension = void 0;
11
+ const path_1 = __importDefault(require("path"));
12
+ const getFileNameExtension = (fullPath) => {
13
+ return path_1.default.extname(fullPath);
14
+ };
15
+ exports.getFileNameExtension = getFileNameExtension;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * This file contains general string helper functions (utils).
4
+ * @note More specific YINI helper functions should go into yiniHelpers.ts-file.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.stripNLAndAfter = exports.isDigit = exports.isAlpha = exports.isEnclosedInBackticks = exports.trimBackticks = void 0;
8
+ exports.splitLines = splitLines;
9
+ const system_1 = require("./system");
10
+ /**
11
+ * Splits a string into an array of lines, handling both LF and CRLF newlines.
12
+ * @param content The input string.
13
+ * @returns Array of lines (strings).
14
+ */
15
+ function splitLines(content) {
16
+ // Chould handle \n (LF), \r\n (CRLF), and even just \r (old Mac style).
17
+ return content.split(/\r\n|\r|\n/);
18
+ }
19
+ /**
20
+ * If a string starts and ends with a backtick `, if so trims the
21
+ * first and last character (the backticks).
22
+ */
23
+ const trimBackticks = (str) => {
24
+ if (str.length >= 2 && str.startsWith('`') && str.endsWith('`')) {
25
+ return str.slice(1, -1);
26
+ }
27
+ return str;
28
+ };
29
+ exports.trimBackticks = trimBackticks;
30
+ /**
31
+ * Returns true if the provided string is enclosed in backticks, e.g. `name`.
32
+ */
33
+ const isEnclosedInBackticks = (str) => {
34
+ if (str.length >= 2 && str.startsWith('`') && str.endsWith('`')) {
35
+ return true;
36
+ }
37
+ return false;
38
+ };
39
+ exports.isEnclosedInBackticks = isEnclosedInBackticks;
40
+ /**
41
+ * Check if the character is A-Z or a-z.
42
+ * @note The string must be of length 1.
43
+ * @param character A character in a string.
44
+ */
45
+ const isAlpha = (character) => {
46
+ if (character.length !== 1) {
47
+ throw Error('Argument into function isAlpha(..) is not of length 1');
48
+ }
49
+ const ch = character;
50
+ if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
51
+ return true;
52
+ }
53
+ return false;
54
+ };
55
+ exports.isAlpha = isAlpha;
56
+ /**
57
+ * Check if the character is a digit (number): 0-9.
58
+ * @note The string must be of length 1.
59
+ * @param character A character in a string.
60
+ */
61
+ const isDigit = (character) => {
62
+ if (character.length !== 1) {
63
+ throw Error('Argument into function isDigit(..) is not of length 1');
64
+ }
65
+ const ch = character;
66
+ if (ch >= '0' && ch <= '9') {
67
+ return true;
68
+ }
69
+ return false;
70
+ };
71
+ exports.isDigit = isDigit;
72
+ /**
73
+ * @returns Returns the beginning up to (but not including) any first
74
+ * encountered newline.
75
+ * @note If no newline is found, returns the whole string.
76
+ * @example
77
+ * `SectionName1
78
+ * //value = 11`
79
+ * => 'SectionName1'
80
+ * @deprecated This seems not useful anymore, use stripCommentsAndAfter(..) instead.
81
+ */
82
+ const stripNLAndAfter = (line) => {
83
+ let idx1 = line.indexOf('\n');
84
+ let idx2 = line.indexOf('\r');
85
+ if (idx1 < 0)
86
+ idx1 = Number.MAX_SAFE_INTEGER;
87
+ if (idx2 < 0)
88
+ idx2 = Number.MAX_SAFE_INTEGER;
89
+ // debugPrint('stripNLAndAfter(..): idx1 = ' + idx1)
90
+ // debugPrint('stripNLAndAfter(..): idx2 = ' + idx2)
91
+ const idx = Math.min(idx1, idx2);
92
+ const resultLine = idx === Number.MAX_SAFE_INTEGER ? line : line.substring(0, idx);
93
+ (0, system_1.debugPrint)('stripNLAndAfter(..), line: >>>' + line + '<<<');
94
+ (0, system_1.debugPrint)('stripNLAndAfter(..), resultLine: >>>' + resultLine + '<<<');
95
+ return resultLine;
96
+ };
97
+ exports.stripNLAndAfter = stripNLAndAfter;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * This file contains general system helper functions (utils).
4
+ * @note More specific YINI helper functions should go into yiniHelpers.ts-file.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.printObject = exports.devPrint = exports.debugPrint = void 0;
8
+ const env_1 = require("../config/env");
9
+ const debugPrint = (str = '') => {
10
+ (0, env_1.isDebug)() && console.debug('DEBUG: ' + str);
11
+ };
12
+ exports.debugPrint = debugPrint;
13
+ const devPrint = (str = '') => {
14
+ (0, env_1.isDev)() && !(0, env_1.isTest)() && console.log('DEV: ' + str);
15
+ };
16
+ exports.devPrint = devPrint;
17
+ const printObject = (obj) => {
18
+ if ((0, env_1.isProd)() || ((0, env_1.isTest)() && !(0, env_1.isDebug)()))
19
+ return;
20
+ const str = JSON.stringify(obj, null, 4);
21
+ console.log(str);
22
+ };
23
+ exports.printObject = printObject;
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ /**
3
+ * This file contains specific YINI helper functions (utils).
4
+ * @note More general helper functions should go into the dir "src/utils/".
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.isValidBacktickedIdent = exports.isValidSimpleIdent = exports.stripCommentsAndAfter = exports.isMarkerCharacter = void 0;
8
+ const string_1 = require("./utils/string");
9
+ const system_1 = require("./utils/system");
10
+ const SECTION_MARKER1 = '^';
11
+ const SECTION_MARKER2 = '~';
12
+ const SECTION_MARKER3 = '\u00A7'; // Section sign §.
13
+ const SECTION_MARKER4 = '\u20AC'; // Euro sign €.
14
+ /**
15
+ * Check if the character is a section marker character.
16
+ * @param character A character in a string.
17
+ * @note The string must be of length 1.
18
+ * @throws Will throw if not exactly of length 1.
19
+ */
20
+ const isMarkerCharacter = (character) => {
21
+ if (character.length !== 1) {
22
+ throw Error('Argument into function isMarkerCharacter(..) is not of length 1');
23
+ }
24
+ const ch = character;
25
+ if (ch === SECTION_MARKER1 ||
26
+ ch === SECTION_MARKER2 ||
27
+ ch === SECTION_MARKER3 ||
28
+ ch === SECTION_MARKER4) {
29
+ return true;
30
+ }
31
+ return false;
32
+ };
33
+ exports.isMarkerCharacter = isMarkerCharacter;
34
+ /**
35
+ * @returns Returns the beginning up to (but not including) any comments
36
+ * starting with //, #, ; or --.
37
+ * @throws Will throw if consisting more than 1 lines.
38
+ */
39
+ const stripCommentsAndAfter = (line) => {
40
+ if ((0, string_1.splitLines)(line).length > 1) {
41
+ throw new Error('Internal error: Detected several row lines in line: >>>' +
42
+ line +
43
+ '<<<');
44
+ }
45
+ let idx1 = line.indexOf('//');
46
+ let idx2 = line.indexOf('# '); // NOTE: (!) Hash comments requires a WS after the hash!
47
+ let idx3 = line.indexOf('#\t'); // NOTE: (!) Hash comments requires a WS after the hash!
48
+ let idx4 = line.indexOf(';');
49
+ let idx5 = line.indexOf('--');
50
+ if (idx1 < 0)
51
+ idx1 = Number.MAX_SAFE_INTEGER;
52
+ if (idx2 < 0)
53
+ idx2 = Number.MAX_SAFE_INTEGER;
54
+ if (idx3 < 0)
55
+ idx3 = Number.MAX_SAFE_INTEGER;
56
+ if (idx4 < 0)
57
+ idx4 = Number.MAX_SAFE_INTEGER;
58
+ if (idx5 < 0)
59
+ idx5 = Number.MAX_SAFE_INTEGER;
60
+ // debugPrint('stripCommentsAndAfter(..): idx1 = ' + idx1)
61
+ // debugPrint('stripCommentsAndAfter(..): idx2 = ' + idx2)
62
+ // debugPrint('stripCommentsAndAfter(..): idx3 = ' + idx3)
63
+ // debugPrint('stripCommentsAndAfter(..): idx4 = ' + idx4)
64
+ // debugPrint('stripCommentsAndAfter(..): idx5 = ' + idx5)
65
+ const idx = Math.min(idx1, idx2, idx3, idx4, idx5);
66
+ const resultLine = idx === Number.MAX_SAFE_INTEGER ? line : line.substring(0, idx);
67
+ (0, system_1.debugPrint)('stripCommentsAndAfter(..), line: >>>' + line + '<<<');
68
+ (0, system_1.debugPrint)('stripCommentsAndAfter(..), resultLine: >>>' + resultLine + '<<<');
69
+ return resultLine;
70
+ };
71
+ exports.stripCommentsAndAfter = stripCommentsAndAfter;
72
+ /**
73
+ * Checks if a string conforms to the identifier rules for section headers and
74
+ * member key names as defined by the YINI specification, following
75
+ * the ANTLR4 lexer rule:
76
+ * IDENT: ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '0'..'9' | '_')*
77
+
78
+ * @throws Will only throw if blank string.
79
+ *
80
+ * @satisfies Should satisfy YINI spec 7, chapter: 3.4. Identifiers, Form 1:
81
+ * Identifier of Simple Form.
82
+ * @link https://github.com/YINI-lang/YINI-spec/blob/develop/YINI-Specification.md#34-identifiers
83
+ */
84
+ const isValidSimpleIdent = (str) => {
85
+ if (!str.trim()) {
86
+ throw Error('Internal error: isValidSimpleIdent(..) received an empty string.');
87
+ }
88
+ // Regex: ^[a-zA-Z_][a-zA-Z0-9_]*$
89
+ return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(str);
90
+ };
91
+ exports.isValidSimpleIdent = isValidSimpleIdent;
92
+ /**
93
+ * Checks if a string is a valid backticked phrase/identifier:
94
+ * - Wrapped in backticks.
95
+ * - No raw tabs, newlines, or control characters (U+0000–U+001F), except as
96
+ * escaped sequences (e.g., \n).
97
+ * - May contain ordinary spaces.
98
+ * @note Empty is allowed: ``, as in spec (due to conform with the JSON empty key "").
99
+ * @throws Will only throw if missing enclosed backtick(s).
100
+ *
101
+ * @satisfies Should satisfy YINI spec 7, chapter: 3.4. Identifiers, Form 2:
102
+ * Backticked Identifier.
103
+ * @link https://github.com/YINI-lang/YINI-spec/blob/develop/YINI-Specification.md#34-identifiers
104
+ */
105
+ const isValidBacktickedIdent = (str) => {
106
+ // if (str.length >= 2 && str.startsWith('`') && str.endsWith('`')) {
107
+ // // OK, and will let possible raw newlines and tabs pass so they
108
+ // // are checked further down.
109
+ // } else {
110
+ if (!(0, string_1.isEnclosedInBackticks)(str)) {
111
+ // NOTE: Only missing backtick(s) should throw error!
112
+ throw Error('Internal error: isValidBacktickedIdent(..) is missing backtick(s) "`".');
113
+ }
114
+ // Get the contents inside backticks.
115
+ const content = str.slice(1, -1);
116
+ // Allowed escapes: \n, \r, \t, \\, \`
117
+ const allowedEscapes = ['n', 'r', 't', '\\', '`'];
118
+ for (let i = 0; i < content.length; i++) {
119
+ const ch = content[i];
120
+ const code = content.charCodeAt(i);
121
+ if (ch === '\\') {
122
+ // If this is an escape, check next char.
123
+ i++;
124
+ if (i >= content.length)
125
+ return false; // Trailing backslash is not valid.
126
+ const nextCh = content[i];
127
+ if (!allowedEscapes.includes(nextCh))
128
+ return false;
129
+ continue;
130
+ }
131
+ // Allow space (U+0020), but not other control chars (U+0000–U+001F).
132
+ if (code < 0x20 && code !== 0x20)
133
+ return false;
134
+ // Disallow raw newlines, tabs, carriage returns, and backtick.
135
+ if (ch === '\n' || ch === '\r' || ch === '\t' || ch === '`')
136
+ // Raw newlines and tabs will yield false.
137
+ return false;
138
+ }
139
+ return true;
140
+ };
141
+ exports.isValidBacktickedIdent = isValidBacktickedIdent;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const src_1 = __importDefault(require("../../../src"));
8
+ const system_1 = require("../../../src/utils/system");
9
+ const DIR_OF_FIXTURES = '../../fixtures/valid/section-nesting-w-classic-markers';
10
+ const answerSectionNestingBigger = {
11
+ Section1: {
12
+ bValue1: true,
13
+ intValue: 1,
14
+ Section11: {
15
+ sValue: 11,
16
+ Section111: {
17
+ sValue: 111,
18
+ intValue: 111,
19
+ },
20
+ },
21
+ Section12: {
22
+ sValue: 12,
23
+ },
24
+ },
25
+ Section2: {
26
+ sValue: 2,
27
+ Section21: {
28
+ sValue: 21,
29
+ bValue: false,
30
+ Section211: {
31
+ sValue: 211,
32
+ Section2111: {
33
+ sValue: 2111,
34
+ },
35
+ Section2112: {
36
+ sValue: 2112,
37
+ strValue: 'test2112',
38
+ },
39
+ },
40
+ },
41
+ Section22: {
42
+ bValue3: true,
43
+ Section221: {
44
+ sValue: 221,
45
+ },
46
+ },
47
+ Section23: {
48
+ bValue3: true,
49
+ },
50
+ },
51
+ };
52
+ /**
53
+ * Parse bigger section nesting as an object.
54
+ */
55
+ describe('Parse bigger section nesting as an object test:', () => {
56
+ const baseDir = path_1.default.join(__dirname, DIR_OF_FIXTURES);
57
+ test('1. Parse bigger section nesting as object, file "section-nesting-bigger.yini".', () => {
58
+ // Arrange.
59
+ const fileName = 'section-nesting-bigger.yini';
60
+ const fullPath = path_1.default.join(baseDir, fileName);
61
+ // Act.
62
+ const result = src_1.default.parseFile(fullPath);
63
+ (0, system_1.debugPrint)('fullPath = ' + fullPath);
64
+ (0, system_1.debugPrint)(result);
65
+ // Assert.
66
+ expect(!!result).toEqual(true);
67
+ expect(result.Section1).not.toBe('this should fail');
68
+ expect(JSON.stringify(result, null, 4)).toEqual(JSON.stringify(answerSectionNestingBigger, null, 4));
69
+ });
70
+ test('2. Parse bigger section nesting as object, file "section-nesting-bigger-w-comments.yini".', () => {
71
+ // Arrange.
72
+ const fileName = 'section-nesting-bigger-w-comments.yini';
73
+ const fullPath = path_1.default.join(baseDir, fileName);
74
+ // Act.
75
+ const result = src_1.default.parseFile(fullPath);
76
+ (0, system_1.debugPrint)('fullPath = ' + fullPath);
77
+ (0, system_1.debugPrint)(result);
78
+ // Assert.
79
+ expect(!!result).toEqual(true);
80
+ expect(result.Section1).not.toBe('this should fail');
81
+ expect(JSON.stringify(result, null, 4)).toEqual(JSON.stringify(answerSectionNestingBigger, null, 4));
82
+ });
83
+ });