s2cfgtojson 3.0.5 → 3.2.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/Struct.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./types.mts";
2
2
  export * from "./enums.mts";
3
- import { DefaultEntries } from "./types.mts";
3
+ import { DefaultEntries, Internal } from "./types.mts";
4
4
 
5
5
  const TAB = " ";
6
6
  const WILDCARD = "_wildcard";
@@ -11,6 +11,17 @@ const KEYWORDS = [
11
11
  "bpatch", // allows patching only specific keys
12
12
  ];
13
13
  const REMOVE_NODE = "removenode";
14
+ const INTERNAL_PROPS = new Set([
15
+ "__internal__",
16
+ "fork",
17
+ "removeNode",
18
+ "addNode",
19
+ "clone",
20
+ "forEach",
21
+ "filter",
22
+ "map",
23
+ "toString",
24
+ ]);
14
25
 
15
26
  /**
16
27
  * This file is part of the Stalker 2 Modding Tools project.
@@ -62,31 +73,94 @@ export class Struct {
62
73
  return this;
63
74
  }
64
75
 
76
+ addNode(value: any, key?: string | number): this {
77
+ if (this.__internal__.isArray !== true) {
78
+ throw new Error("Cannot add node to non-array struct.");
79
+ }
80
+ if (key === undefined) {
81
+ const nextIndex = Object.keys(this)
82
+ .map((k) => parseInt(k))
83
+ .filter((k) => !isNaN(k))
84
+ .sort((a, b) => a - b)
85
+ .pop();
86
+ this[nextIndex !== undefined ? nextIndex + 1 : 0] = value;
87
+ } else {
88
+ this[key] = value;
89
+ }
90
+ return this;
91
+ }
92
+
65
93
  clone() {
66
94
  return Struct.fromString(this.toString())[0] as this;
67
95
  }
68
96
 
97
+ entries(): [keyof this, (typeof this)[keyof this]][] {
98
+ return Object.entries(this).filter(([key]) => !INTERNAL_PROPS.has(key)) as [
99
+ keyof this,
100
+ (typeof this)[keyof this],
101
+ ][];
102
+ }
103
+
104
+ forEach<K extends Exclude<keyof this, Internal>, V extends (typeof this)[K]>(
105
+ callback: ([key, value]: [K, V]) => void,
106
+ ): void {
107
+ this.entries().forEach(([key, value]) => callback([key as K, value as V]));
108
+ }
109
+
110
+ /**
111
+ * Filters the struct entries based on a callback function. Returns a copy.
112
+ * @param callback
113
+ */
114
+ filter<K extends Exclude<keyof this, Internal>, V extends (typeof this)[K]>(
115
+ callback: ([key, value]: [K, V]) => boolean,
116
+ ): Partial<this> & Struct {
117
+ const clone = this.clone();
118
+ clone.entries().forEach(([key, value]) => {
119
+ if (!callback([key as K, value as V])) {
120
+ delete clone[key];
121
+ }
122
+ });
123
+ return clone;
124
+ }
125
+
126
+ /**
127
+ * Maps the struct entries based on a callback function. Returns a copy.
128
+ * @param callback
129
+ */
130
+ map<K extends Exclude<keyof this, Internal>, V extends (typeof this)[K]>(
131
+ callback: ([key, value]: [K, V]) => V,
132
+ ): this {
133
+ const clone = this.clone();
134
+ clone.entries().forEach(([key, value]) => {
135
+ clone[key] = callback([key as K, value as V]);
136
+ });
137
+ return clone;
138
+ }
139
+
69
140
  toString(): string {
70
141
  if (!(this.__internal__ instanceof Refs)) {
71
142
  this.__internal__ = new Refs(this.__internal__);
72
143
  }
73
- const { __internal__: internal, ...entries } = this;
74
144
 
75
- let text: string = internal.rawName ? `${internal.rawName} : ` : "";
145
+ let text: string = this.__internal__.rawName
146
+ ? `${this.__internal__.rawName} : `
147
+ : "";
76
148
  text += "struct.begin";
77
149
 
78
- const refs = internal.toString();
150
+ const refs = this.__internal__.toString();
79
151
  if (refs) {
80
152
  text += ` {${refs}}`;
81
153
  }
82
154
 
83
155
  text += "\n";
84
156
  // Add all keys
85
- text += Object.entries(entries || {})
157
+ text += Object.entries(this)
158
+ .filter(([key]) => !INTERNAL_PROPS.has(key))
86
159
  .map(([key, value]) => {
87
160
  const nameAlreadyRendered =
88
161
  value instanceof Struct && value.__internal__.rawName;
89
- const useAsterisk = internal.isArray && internal.useAsterisk;
162
+ const useAsterisk =
163
+ this.__internal__.isArray && this.__internal__.useAsterisk;
90
164
  let keyOrIndex = "";
91
165
  let equalsOrColon = "";
92
166
  let spaceOrNoSpace = "";
@@ -117,7 +191,7 @@ export class Refs implements DefaultEntries {
117
191
  isArray?: boolean;
118
192
  useAsterisk?: boolean;
119
193
 
120
- constructor(ref?: string | object) {
194
+ constructor(ref?: string | Refs) {
121
195
  if (typeof ref === "string") {
122
196
  ref
123
197
  .split(";")
@@ -151,7 +225,7 @@ export class Refs implements DefaultEntries {
151
225
  }
152
226
 
153
227
  const structHeadRegex = new RegExp(
154
- `^(.*)\\s*:\\s*struct\\.begin\\s*({\\s*((${KEYWORDS.join("|")})\\s*(=.+)?)\\s*})?`,
228
+ `^\s*(.*)\\s*:\\s*struct\\.begin\\s*({\\s*((${KEYWORDS.join("|")})\\s*(=.+)?)\\s*})?`,
155
229
  );
156
230
 
157
231
  function parseHead(line: string, index: number): Struct {
package/Struct.test.mts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  pad,
6
6
  Struct,
7
7
  ArmorPrototype,
8
+ Refs,
8
9
  } from "./Struct.mjs";
9
10
  import fs from "node:fs";
10
11
 
@@ -26,15 +27,23 @@ class TradePrototype extends Struct {
26
27
  TradeGenerators = new TradeGenerators();
27
28
  }
28
29
  class TradeGenerators extends Struct {
29
- "*" = new TradeGenerator();
30
+ __internal__ = new Refs({
31
+ isArray: true,
32
+ useAsterisk: true,
33
+ });
34
+ "0" = new TradeGenerator();
30
35
  }
31
36
  class TradeGenerator extends Struct {
32
37
  BuyLimitations = new BuyLimitations();
33
38
  }
34
39
 
35
40
  class BuyLimitations extends Struct {
36
- [0] = "EItemType::Weapon";
37
- [1] = "EItemType::Armor";
41
+ __internal__ = new Refs({
42
+ rawName: "BuyLimitations",
43
+ isArray: true,
44
+ });
45
+ "0" = "EItemType::Weapon";
46
+ "1" = "EItemType::Armor";
38
47
  }
39
48
 
40
49
  describe("Struct", () => {
@@ -236,6 +245,55 @@ struct.end`;
236
245
  });
237
246
  });
238
247
 
248
+ describe("addNode", () => {
249
+ test("1", () => {
250
+ const a = new TradePrototype().fork(true);
251
+ expect(a.TradeGenerators[0].BuyLimitations[0]).toBe("EItemType::Weapon");
252
+ expect(a.TradeGenerators[0].BuyLimitations[1]).toBe("EItemType::Armor");
253
+ a.TradeGenerators[0].BuyLimitations.addNode("EItemType::Artifact");
254
+ expect(a.TradeGenerators[0].BuyLimitations[2]).toBe(
255
+ "EItemType::Artifact",
256
+ );
257
+ });
258
+ });
259
+
260
+ describe("forEach", () => {
261
+ test("1", () => {
262
+ const a = new TradePrototype().fork(true);
263
+ expect(a.TradeGenerators[0].BuyLimitations[0]).toBe("EItemType::Weapon");
264
+ expect(a.TradeGenerators[0].BuyLimitations[1]).toBe("EItemType::Armor");
265
+ a.TradeGenerators[0].BuyLimitations.forEach(([k]) => {
266
+ a.TradeGenerators[0].BuyLimitations[k] = "forEach";
267
+ });
268
+ expect(a.TradeGenerators[0].BuyLimitations[0]).toBe("forEach");
269
+ expect(a.TradeGenerators[0].BuyLimitations[1]).toBe("forEach");
270
+ });
271
+ });
272
+
273
+ describe("filter", () => {
274
+ test("1", () => {
275
+ const a = new TradePrototype().fork(true);
276
+ expect(a.TradeGenerators[0].BuyLimitations[0]).toBe("EItemType::Weapon");
277
+ expect(a.TradeGenerators[0].BuyLimitations[1]).toBe("EItemType::Armor");
278
+ const b = a.TradeGenerators[0].BuyLimitations.filter(([k]) => k === "0");
279
+ expect(b[0]).toBe("EItemType::Weapon");
280
+ expect(b[1]).toBeUndefined();
281
+ });
282
+ });
283
+
284
+ describe("map", () => {
285
+ test("1", () => {
286
+ const a = new TradePrototype();
287
+ expect(a.TradeGenerators[0].BuyLimitations[0]).toBe("EItemType::Weapon");
288
+ expect(a.TradeGenerators[0].BuyLimitations[1]).toBe("EItemType::Armor");
289
+ const b = a.TradeGenerators[0].BuyLimitations.map(
290
+ ([k, v]) => `${v}-mapped-${k}`,
291
+ );
292
+ expect(b[0]).toBe("EItemType::Weapon-mapped-0");
293
+ expect(b[1]).toBe("EItemType::Armor-mapped-1");
294
+ });
295
+ });
296
+
239
297
  describe("clone", () => {
240
298
  test("1", () => {
241
299
  const a = new TradePrototype();
@@ -251,7 +309,7 @@ struct.end`;
251
309
  const a = new TradePrototype().fork(true);
252
310
  expect(a.TradeGenerators[0].BuyLimitations[0]).toBe("EItemType::Weapon");
253
311
  expect(a.TradeGenerators[0].BuyLimitations[1]).toBe("EItemType::Armor");
254
- a.TradeGenerators[0].BuyLimitations.removeNode(0);
312
+ a.TradeGenerators[0].BuyLimitations.removeNode("0");
255
313
  expect(a.TradeGenerators[0].BuyLimitations[0]).toBe("removenode");
256
314
  });
257
315
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "s2cfgtojson",
3
- "version": "3.0.5",
3
+ "version": "3.2.0",
4
4
  "description": "Converts Stalker 2 Cfg file into POJOs",
5
5
  "keywords": [
6
6
  "stalker",
package/types.mts CHANGED
@@ -68,7 +68,17 @@ import {
68
68
  } from "./enums.mjs";
69
69
  import { Struct } from "./Struct.mjs";
70
70
 
71
- export type Internal = "__internal__";
71
+ export type Internal =
72
+ | "__internal__"
73
+ | "fork"
74
+ | "removeNode"
75
+ | "addNode"
76
+ | "clone"
77
+ | "forEach"
78
+ | "filter"
79
+ | "map"
80
+ | "entries"
81
+ | "toString";
72
82
 
73
83
  export type DeeplyPartial<T> = {
74
84
  [P in keyof T]?: T[P] extends Array<infer U>
@@ -81,7 +91,7 @@ export type DeeplyPartial<T> = {
81
91
  };
82
92
 
83
93
  export type DeeplyPartialStruct<T> = DeeplyPartial<
84
- T extends Struct ? Omit<T, Internal> : T
94
+ T extends Struct ? Exclude<T, Internal> : T
85
95
  >;
86
96
 
87
97
  export interface DefaultEntries {