Skip to content

Commit 9065278

Browse files
vaisestLocalIdentity
andauthored
Refactor stringify, add tests for it, and use it in some exports (#2347)
* Refactor stringify, add tests for it, and use it to simplify exports. * Update legionpassives * Fix stringify performance scaling * Tiny performance improvements * Convert mods.lua to use saveTableToFile * Fix mods.lua * Revert "Fix mods.lua" This reverts commit c0b3cf1. * Fix stringify for normal lua strings Use Lua's quoted-string formatting to safely escape quotes, backslashes, control characters, and newlines. Remove the now-redundant manual escaping from the flavour text exporter and add tests for strings and keys. Fixes the export of tags for some mods as I didn't realise that was an issue --------- Co-authored-by: LocalIdentity <localidentity2@gmail.com>
1 parent 29ab826 commit 9065278

17 files changed

Lines changed: 13687 additions & 13418 deletions

spec/System/TestItemMods_spec.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ describe("TetsItemMods", function()
77
-- newBuild() takes care of resetting everything in setup()
88
end)
99

10+
it("retains mod tags without generation weight multipliers", function()
11+
local mod = data.itemMods.Item.IgniteChanceIncrease1
12+
13+
assert.same({ "no_cold_spell_mods", "no_lightning_spell_mods", "no_chaos_spell_mods" }, mod.tags)
14+
assert.is_nil(mod.weightMultiplierKey)
15+
end)
16+
1017
it("shows duplicate selected variants in item tooltips when enabled", function()
1118
local item = new("Item", [[
1219
Rarity: Unique

spec/System/TestUtils_spec.lua

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
describe("Utils.stringify", function()
2+
local utils = require("Modules/Utils")
3+
4+
-- Parses stringify output back into a Lua value
5+
local function serializeAndLoad(value, allowNewlines)
6+
local str = utils.stringify(value, allowNewlines)
7+
local chunk, err = loadstring("return "..str)
8+
assert.is_truthy(chunk)
9+
return chunk(), str
10+
end
11+
12+
describe("scalars", function()
13+
it("stringifies strings with quotes", function()
14+
assert.equal('"hello"', utils.stringify("hello"))
15+
end)
16+
17+
it("strips newlines from strings by default", function()
18+
assert.equal('"a b"', utils.stringify("a\nb"))
19+
end)
20+
21+
it("preserves newlines when allowed", function()
22+
local out = serializeAndLoad("a\nb", true)
23+
assert.equal("a\nb", out)
24+
end)
25+
26+
it("does not use long string form for newline-free strings", function()
27+
assert.equal('"ab"', utils.stringify("ab", true))
28+
end)
29+
30+
it("escapes quotes and backslashes", function()
31+
local input = 'a"b\\c'
32+
local out = serializeAndLoad(input, true)
33+
assert.equal(input, out)
34+
end)
35+
36+
it("preserves carriage returns and long-string delimiters when allowed", function()
37+
local input = "a\r\n]]b"
38+
local out = serializeAndLoad(input, true)
39+
assert.equal(input, out)
40+
end)
41+
42+
it("normalizes all newline forms when newlines are disabled", function()
43+
assert.equal('"a b c"', utils.stringify("a\r\nb\rc"))
44+
end)
45+
46+
it("stringifies numbers", function()
47+
assert.equal("42", utils.stringify(42))
48+
assert.equal("-3.5", utils.stringify(-3.5))
49+
end)
50+
51+
it("stringifies booleans", function()
52+
assert.equal("true", utils.stringify(true))
53+
assert.equal("false", utils.stringify(false))
54+
end)
55+
56+
it("stringifies nil", function()
57+
assert.equal("nil", utils.stringify(nil))
58+
end)
59+
60+
-- supposedly these are valid table keys, but like come on
61+
it("errors on disallowed types", function()
62+
assert.has_error(function() utils.stringify(function() end) end)
63+
assert.has_error(function() utils.stringify({[ function() end ] = "hello"}) end)
64+
end)
65+
end)
66+
67+
describe("tables", function()
68+
it("serializes and loads an empty table", function()
69+
local out = serializeAndLoad({})
70+
assert.same({}, out)
71+
end)
72+
73+
it("serializes and loads an array using array syntax", function()
74+
local input = { 1, 2, 3 }
75+
local out, str = serializeAndLoad(input)
76+
assert.same(input, out)
77+
-- array entries should not include explicit keys
78+
assert.is_nil(str:find("%[1%]"))
79+
end)
80+
81+
it("serializes and loads a string-keyed map", function()
82+
local input = { foo = "bar", baz = 1 }
83+
local out = serializeAndLoad(input)
84+
assert.same(input, out)
85+
end)
86+
87+
it("serializes and loads nested tables (no mixed array/map levels)", function()
88+
local input = { a = { b = { c = { deep = true, value = 3 } } }, list = { "x", "y" } }
89+
local out = serializeAndLoad(input)
90+
assert.same(input, out)
91+
end)
92+
93+
it("sorts map keys deterministically", function()
94+
local str = utils.stringify({ c = 1, a = 1, b = 1 })
95+
local posA = str:find('%["a"%]')
96+
local posB = str:find('%["b"%]')
97+
local posC = str:find('%["c"%]')
98+
assert.is_truthy(posA < posB and posB < posC)
99+
end)
100+
101+
it("serializes and loads numeric (non-sequential) keys", function()
102+
local input = { [5] = "five", [10] = "ten" }
103+
local out = serializeAndLoad(input)
104+
assert.same(input, out)
105+
end)
106+
107+
it("serializes and loads multiline string values when allowed", function()
108+
local input = { text = "line1\nline2" }
109+
local out = serializeAndLoad(input, true)
110+
assert.same(input, out)
111+
end)
112+
113+
it("serializes and loads escaped multiline string keys", function()
114+
local input = { ['a"\\b\n]]c'] = true }
115+
local out = serializeAndLoad(input, true)
116+
assert.same(input, out)
117+
end)
118+
119+
it("serializes and loads mixed tables", function()
120+
local input = {"one", "two", six = 7, 3, 4, five = "six"}
121+
local str = utils.stringify(input, false, 1)
122+
assert.equal([[{
123+
"one",
124+
"two",
125+
3,
126+
4,
127+
["five"] = "six",
128+
["six"] = 7,
129+
}]], str)
130+
local out = serializeAndLoad(input)
131+
assert.same(input, out)
132+
end)
133+
end)
134+
end)

src/Classes/TradeQueryGenerator.lua

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ local m_max = math.max
1010
local s_format = string.format
1111
local t_insert = table.insert
1212
local tradeHelpers = LoadModule("Classes/TradeHelpers")
13+
local utils = LoadModule("Modules/Utils")
1314

1415
-- string are an any type while tables require all fields to be matched with type and subType require both to be matched exactly. [1] type, [2] subType, subType is optional and must be nil if not present.
1516
local tradeCategoryNames = {
@@ -395,11 +396,6 @@ function TradeQueryGeneratorClass:InitMods()
395396
error("Error received from api/trade2/data/stats: "..body.error.message)
396397
end
397398

398-
local f = io.open("./Data/TradeSiteStats.lua", "w")
399-
if not f then
400-
error("Could not open file for writing trade stat data")
401-
end
402-
403399
for catIdx, _ in ipairs(body.result) do
404400
table.sort(body.result[catIdx].entries, function(a, b)
405401
if a.text == b.text then
@@ -409,14 +405,8 @@ function TradeQueryGeneratorClass:InitMods()
409405
end)
410406
end
411407

412-
local template = [[-- This file is automatically downloaded, do not edit!
413-
-- Trade site stat data (c) Grinding Gear Games
414-
-- https://www.pathofexile.com/api/trade2/data/stats
415-
-- spell-checker: disable
416-
return %s
417-
-- spell-checker: enable]]
418-
f:write(s_format(template, stringify(body.result)))
419-
f:close()
408+
local description = "This file contains the trade site data from https://www.pathofexile.com/api/trade2/data/stats"
409+
utils.saveTableToFile("./Data/TradeSiteStats.lua", body.result, description)
420410

421411
self.modData = {
422412
["Explicit"] = { },
@@ -636,19 +626,12 @@ return %s
636626
end
637627
end
638628

639-
local queryModsFile = io.open(queryModFilePath, 'w')
640-
queryModsFile:write([[-- This file is automatically generated, do not edit!
641-
-- Stat data (c) Grinding Gear Games
642-
643-
-- This file contains categories of stats, mapped from trade hash to details
644-
-- relevant for generating search weights Note that the trade site requires a
645-
-- prefix of e.g. explicit.stat_{hash}. See
646-
-- https://www.pathofexile.com/api/trade2/data/stats for a list of all trade
647-
-- site stats.
648-
649-
]])
650-
queryModsFile:write("return " .. stringify(self.modData))
651-
queryModsFile:close()
629+
local qmDescription = [[This file contains categories of stats, mapped from trade hash to details
630+
relevant for generating search weights Note that the trade site requires a
631+
prefix of e.g. explicit.stat_{hash}. See
632+
TradeSiteStats.lua for a list of all trade
633+
site stats.]]
634+
utils.saveTableToFile(queryModFilePath, self.modData, qmDescription)
652635
end
653636

654637
function TradeQueryGeneratorClass:GenerateModWeights(modsToTest)

src/Classes/TradeQueryRequests.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
--
66

77
local dkjson = require "dkjson"
8+
local utils = LoadModule("Modules/Utils")
89

910
---@class TradeQueryRequests
1011
local TradeQueryRequestsClass = newClass("TradeQueryRequests", function(self, rateLimiter)
@@ -219,7 +220,7 @@ function TradeQueryRequestsClass:PerformSearch(realm, league, query, callback)
219220
if response.error then
220221
if not (response.error.code and response.error.message) then
221222
errMsg = "Encountered unknown error, check console for details."
222-
ConPrintf("Unknown error: %s", stringify(response.error))
223+
ConPrintf("Unknown error: %s", utils.stringify(response.error))
223224
callback(response, errMsg)
224225
end
225226
if response.error.message:find("Logging in will increase this limit") then

0 commit comments

Comments
 (0)