5etools-utils 0.4.10 → 0.5.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/lib/Api.js CHANGED
@@ -6,6 +6,8 @@ import {BrewCleaner} from "./BrewCleaner.js";
6
6
  import {BrewTimestamper} from "./BrewTimestamper.js";
7
7
  import {BrewTester} from "./BrewTester.js";
8
8
  import {getCleanJson} from "./UtilClean.js";
9
+ import {DataTester, DataTesterBase, BraceCheck, EscapeCharacterCheck} from "./TestData.js";
10
+ import {ObjectWalker} from "./ObjectWalker.js";
9
11
 
10
12
  export {
11
13
  JsonTester,
@@ -16,4 +18,9 @@ export {
16
18
  BrewTimestamper,
17
19
  BrewTester,
18
20
  getCleanJson,
21
+ DataTester,
22
+ DataTesterBase,
23
+ BraceCheck,
24
+ EscapeCharacterCheck,
25
+ ObjectWalker,
19
26
  };
@@ -0,0 +1,83 @@
1
+ class ObjectWalker {
2
+ static _runHandlers (
3
+ {
4
+ primitiveHandlers,
5
+ to,
6
+ obj,
7
+ filePath,
8
+ lastType,
9
+ lastKey,
10
+ },
11
+ ) {
12
+ if (!primitiveHandlers[to]) return;
13
+
14
+ primitiveHandlers[to] instanceof Array
15
+ ? primitiveHandlers[to].forEach(ph => ph(obj, {filePath, lastType, lastKey}))
16
+ : primitiveHandlers[to](obj, {filePath, lastType, lastKey});
17
+ }
18
+
19
+ static walk (
20
+ {
21
+ obj,
22
+ filePath,
23
+ primitiveHandlers,
24
+ lastType,
25
+ lastKey,
26
+ },
27
+ ) {
28
+ const to = typeof obj;
29
+
30
+ const [opts] = arguments;
31
+ const nxtOpts = {...opts};
32
+
33
+ if (obj === null) {
34
+ this._runHandlers({
35
+ ...nxtOpts,
36
+ to: "null",
37
+ });
38
+
39
+ return obj;
40
+ }
41
+
42
+ switch (to) {
43
+ case undefined:
44
+ case "boolean":
45
+ case "number":
46
+ case "string": {
47
+ this._runHandlers({
48
+ ...nxtOpts,
49
+ to,
50
+ });
51
+
52
+ return obj;
53
+ }
54
+
55
+ case "object": {
56
+ if (obj instanceof Array) {
57
+ this._runHandlers({
58
+ ...nxtOpts,
59
+ to: "array",
60
+ });
61
+
62
+ return obj.map(it => this.walk({obj: it, filePath, primitiveHandlers, lastType, lastKey}));
63
+ }
64
+
65
+ this._runHandlers({
66
+ ...nxtOpts,
67
+ to: "object",
68
+ });
69
+
70
+ Object.keys(obj)
71
+ .forEach(k => {
72
+ obj[k] = this.walk({obj: obj[k], filePath, primitiveHandlers, lastType, lastKey: k});
73
+ });
74
+
75
+ return obj;
76
+ }
77
+
78
+ default: throw new Error(`Unhandled type "${to}"`);
79
+ }
80
+ }
81
+ }
82
+
83
+ export {ObjectWalker};
@@ -0,0 +1,188 @@
1
+ import fs from "fs";
2
+ import {readJSON} from "./UtilFs.js";
3
+ import {ObjectWalker} from "./ObjectWalker.js";
4
+
5
+ /** Runs multiple handlers on each file, to avoid re-reading each file for each handler */
6
+ class _ParsedJsonChecker {
7
+ static _CLAZZES_FILE_HANDLER = [];
8
+
9
+ static _PRIMITIVE_HANDLERS = {};
10
+
11
+ static registerFileHandler (Clazz) {
12
+ _ParsedJsonChecker._CLAZZES_FILE_HANDLER.push(Clazz);
13
+ }
14
+
15
+ static addPrimitiveHandler (primitiveType, handler) {
16
+ (this._PRIMITIVE_HANDLERS[primitiveType] = this._PRIMITIVE_HANDLERS[primitiveType] || [])
17
+ .push(handler);
18
+ }
19
+
20
+ /* -------------------------------------------- */
21
+
22
+ static run (filePath, {fnIsIgnoredFile, fnIsIgnoredDirector} = {}) {
23
+ this._fileRecurse({
24
+ filePath,
25
+ fnIsIgnoredFile,
26
+ fnIsIgnoredDirector,
27
+ });
28
+ }
29
+
30
+ static _fileRecurse ({filePath, fnIsIgnoredFile, fnIsIgnoredDirector}) {
31
+ if (fs.lstatSync(filePath).isDirectory()) {
32
+ if (fnIsIgnoredDirector && fnIsIgnoredDirector(filePath)) return;
33
+
34
+ return fs.readdirSync(filePath)
35
+ .forEach(nxt => this._fileRecurse({filePath: `${filePath}/${nxt}`, fnIsIgnoredFile, fnIsIgnoredDirector}));
36
+ }
37
+
38
+ if (!filePath.endsWith(".json") || (fnIsIgnoredFile && fnIsIgnoredFile(filePath))) return;
39
+
40
+ const contents = readJSON(filePath);
41
+
42
+ ObjectWalker.walk({
43
+ obj: contents,
44
+ filePath,
45
+ primitiveHandlers: this._PRIMITIVE_HANDLERS,
46
+ });
47
+
48
+ this._CLAZZES_FILE_HANDLER.forEach(dataTester => dataTester.handleFile(filePath, contents));
49
+ this._CLAZZES_FILE_HANDLER.forEach(dataTester => dataTester.addMessageBoundary());
50
+ }
51
+ }
52
+
53
+ class DataTesterBase {
54
+ static _MESSAGE = "";
55
+
56
+ static _addMessage (str) {
57
+ this._MESSAGE += str;
58
+ }
59
+
60
+ static addMessageBoundary () {
61
+ if (!this._MESSAGE.trim()) return;
62
+ if (this._MESSAGE.trim().slice(-5) === "\n---\n") return;
63
+ this._MESSAGE = this._MESSAGE.trimEnd();
64
+ this._MESSAGE += `\n---\n`;
65
+ }
66
+
67
+ static getMessage () { return this._MESSAGE; }
68
+
69
+ /* -------------------------------------------- */
70
+
71
+ static async pRun () { /* Implement as required */ }
72
+
73
+ static async pPostRun () { /* Implement as required */ }
74
+
75
+ /* -------------------------------------------- */
76
+
77
+ static registerParsedFileCheckers (parsedJsonChecker) { /* Implement as required */ }
78
+
79
+ static registerParsedPrimitiveHandlers (parsedJsonChecker) { /* Implement as required */ }
80
+
81
+ /* -------------------------------------------- */
82
+
83
+ static handleFile (filePath, contents) { /* Implement as required */ }
84
+ }
85
+
86
+ class BraceCheck extends DataTesterBase {
87
+ static registerParsedPrimitiveHandlers (parsedJsonChecker) {
88
+ parsedJsonChecker.addPrimitiveHandler("string", this._checkString.bind(this));
89
+ }
90
+
91
+ static _checkString (str, {filePath}) {
92
+ let total = 0;
93
+ for (let i = 0; i < str.length; ++i) {
94
+ const c = str[i];
95
+ switch (c) {
96
+ case "{":
97
+ ++total;
98
+ break;
99
+ case "}":
100
+ --total;
101
+ break;
102
+ }
103
+ }
104
+ if (total !== 0) {
105
+ this._addMessage(`Mismatched braces in ${filePath}: "${str}"\n`);
106
+ }
107
+ }
108
+ }
109
+
110
+ class EscapeCharacterCheck extends DataTesterBase {
111
+ static _CHARS = 16;
112
+
113
+ static _errors = [];
114
+
115
+ static registerParsedFileCheckers (parsedJsonChecker) {
116
+ parsedJsonChecker.registerFileHandler(this);
117
+ }
118
+
119
+ static _checkString (str) {
120
+ let re = /([\n\t\r])/g;
121
+ let m;
122
+ while ((m = re.exec(str))) {
123
+ const startIx = Math.max(m.index - this._CHARS, 0);
124
+ const endIx = Math.min(m.index + this._CHARS, str.length);
125
+ this._errors.push(`...${str.substring(startIx, endIx)}...`.replace(/[\n\t\r]/g, (...m) => m[0] === "\n" ? "***\\n***" : m[0] === "\t" ? "***\\t***" : "***\\r***"));
126
+ }
127
+ }
128
+
129
+ static handleFile (file, contents) {
130
+ this._errors = [];
131
+ ObjectWalker.walk({
132
+ obj: contents,
133
+ filePath: file,
134
+ primitiveHandlers: {
135
+ string: this._checkString.bind(this),
136
+ },
137
+ });
138
+ if (this._errors.length) {
139
+ this._addMessage(`Unwanted escape characters in ${file}! See below:\n`);
140
+ this._addMessage(`\t${this._errors.join("\n\t")}`);
141
+ }
142
+ }
143
+ }
144
+
145
+ class DataTester {
146
+ static async pRun (
147
+ filePath,
148
+ ClazzDataTesters,
149
+ {
150
+ fnIsIgnoredFile,
151
+ fnIsIgnoredDirector,
152
+ } = {},
153
+ ) {
154
+ ClazzDataTesters.forEach(ClazzDataTester => {
155
+ ClazzDataTester.registerParsedPrimitiveHandlers(_ParsedJsonChecker);
156
+ ClazzDataTester.registerParsedFileCheckers(_ParsedJsonChecker);
157
+ });
158
+ _ParsedJsonChecker.run(filePath, {fnIsIgnoredFile, fnIsIgnoredDirector});
159
+
160
+ for (const ClazzDataTester of ClazzDataTesters) {
161
+ await ClazzDataTester.pRun();
162
+ }
163
+
164
+ for (const ClazzDataTester of ClazzDataTesters) {
165
+ await ClazzDataTester.pPostRun();
166
+ }
167
+ }
168
+
169
+ static getLogReport (ClazzDataTesters) {
170
+ let outMessage = "";
171
+ for (const ClazzDataTester of ClazzDataTesters) {
172
+ const pt = ClazzDataTester.getMessage();
173
+ if (pt) outMessage += `Error messages for ${ClazzDataTester.name}:\n\n${pt}\n`;
174
+ else console.log(`##### ${ClazzDataTester.name} passed! #####`);
175
+ }
176
+
177
+ if (outMessage) console.error(outMessage);
178
+
179
+ return outMessage;
180
+ }
181
+ }
182
+
183
+ export {
184
+ DataTester,
185
+ DataTesterBase,
186
+ BraceCheck,
187
+ EscapeCharacterCheck,
188
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "5etools-utils",
3
- "version": "0.4.10",
3
+ "version": "0.5.0",
4
4
  "description": "Shared utilities for the 5etools ecosystem.",
5
5
  "type": "module",
6
6
  "main": "lib/Api.js",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "encounters.json",
4
- "version": "1.2.5",
4
+ "version": "1.3.0",
5
5
  "title": "EncounterArray",
6
6
  "type": "object",
7
7
  "$defs": {
@@ -37,8 +37,8 @@
37
37
  "maxlvl": {
38
38
  "type": "integer"
39
39
  },
40
- "diceType": {
41
- "type": "integer"
40
+ "diceExpression": {
41
+ "type": "string"
42
42
  },
43
43
  "rollAttitude": {
44
44
  "type": "boolean"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "names.json",
4
- "version": "1.2.3",
4
+ "version": "1.3.0",
5
5
  "title": "NameArray",
6
6
  "type": "object",
7
7
  "$defs": {
@@ -31,8 +31,8 @@
31
31
  "option": {
32
32
  "type": "string"
33
33
  },
34
- "diceType": {
35
- "type": "integer"
34
+ "diceExpression": {
35
+ "type": "string"
36
36
  },
37
37
  "table": {
38
38
  "type": "array",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "encounters.json",
4
- "version": "1.2.5",
4
+ "version": "1.3.0",
5
5
  "title": "EncounterArray",
6
6
  "type": "object",
7
7
  "$defs": {
@@ -37,8 +37,8 @@
37
37
  "maxlvl": {
38
38
  "type": "integer"
39
39
  },
40
- "diceType": {
41
- "type": "integer"
40
+ "diceExpression": {
41
+ "type": "string"
42
42
  },
43
43
  "rollAttitude": {
44
44
  "type": "boolean"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "names.json",
4
- "version": "1.2.3",
4
+ "version": "1.3.0",
5
5
  "title": "NameArray",
6
6
  "type": "object",
7
7
  "$defs": {
@@ -31,8 +31,8 @@
31
31
  "option": {
32
32
  "type": "string"
33
33
  },
34
- "diceType": {
35
- "type": "integer"
34
+ "diceExpression": {
35
+ "type": "string"
36
36
  },
37
37
  "table": {
38
38
  "type": "array",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "encounters.json",
4
- "version": "1.2.5",
4
+ "version": "1.3.0",
5
5
  "title": "EncounterArray",
6
6
  "type": "object",
7
7
  "$defs": {
@@ -37,8 +37,8 @@
37
37
  "maxlvl": {
38
38
  "type": "integer"
39
39
  },
40
- "diceType": {
41
- "type": "integer"
40
+ "diceExpression": {
41
+ "type": "string"
42
42
  },
43
43
  "rollAttitude": {
44
44
  "type": "boolean"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "names.json",
4
- "version": "1.2.3",
4
+ "version": "1.3.0",
5
5
  "title": "NameArray",
6
6
  "type": "object",
7
7
  "$defs": {
@@ -31,8 +31,8 @@
31
31
  "option": {
32
32
  "type": "string"
33
33
  },
34
- "diceType": {
35
- "type": "integer"
34
+ "diceExpression": {
35
+ "type": "string"
36
36
  },
37
37
  "table": {
38
38
  "type": "array",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "encounters.json",
4
- "version": "1.2.5",
4
+ "version": "1.3.0",
5
5
  "title": "EncounterArray",
6
6
  "type": "object",
7
7
  "$defs": {
@@ -37,8 +37,8 @@
37
37
  "maxlvl": {
38
38
  "type": "integer"
39
39
  },
40
- "diceType": {
41
- "type": "integer"
40
+ "diceExpression": {
41
+ "type": "string"
42
42
  },
43
43
  "rollAttitude": {
44
44
  "type": "boolean"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "names.json",
4
- "version": "1.2.3",
4
+ "version": "1.3.0",
5
5
  "title": "NameArray",
6
6
  "type": "object",
7
7
  "$defs": {
@@ -31,8 +31,8 @@
31
31
  "option": {
32
32
  "type": "string"
33
33
  },
34
- "diceType": {
35
- "type": "integer"
34
+ "diceExpression": {
35
+ "type": "string"
36
36
  },
37
37
  "table": {
38
38
  "type": "array",