@@ -20,6 +20,7 @@ const bufferedReader = std.io.bufferedReader;
2020const bufferedWriter = std .io .bufferedWriter ;
2121const copyFileAbsolute = std .fs .copyFileAbsolute ;
2222const endsWith = std .mem .endsWith ;
23+ const indexOf = std .mem .indexOf ;
2324const eql = std .mem .eql ;
2425const expectEqualStrings = std .testing .expectEqualStrings ;
2526const extension = std .fs .path .extension ;
@@ -172,6 +173,10 @@ pub fn main() !void {
172173 std .log .info ("Error: Invalid output path" , .{});
173174 return err ;
174175 },
176+ error .InvalidRulesPath = > {
177+ std .log .info ("Error: Invalid rules path" , .{});
178+ return err ;
179+ },
175180 error .UnexpectedToken = > {
176181 std .log .info ("Error: Unexpected '{s}' at {s}:{}:{}\n " , .{
177182 diagnostics .token orelse "null" ,
@@ -216,7 +221,7 @@ pub fn convert(input_folder_path_: []const u8, output_folder_path_: []const u8,
216221 // https://github.com/ziglang/zig/issues/15607#issue-1698930560
217222 if (! try isValidDirPath (input_folder_path_ )) return error .InvalidInputPath ;
218223 if (! try isValidDirPath (output_folder_path_ )) return error .InvalidOutputPath ;
219- if (! try isValidDirPath (rules_folder_path_ )) return error .InvalidOutputPath ;
224+ if (! try isValidDirPath (rules_folder_path_ )) return error .InvalidRulesPath ;
220225
221226 const input_folder_path = try std .fs .realpathAlloc (allocator , input_folder_path_ );
222227 const output_folder_path = try std .fs .realpathAlloc (allocator , output_folder_path_ );
@@ -285,6 +290,10 @@ pub fn convert(input_folder_path_: []const u8, output_folder_path_: []const u8,
285290 std .log .info ("Applying INI SoundContainer rules...\n " , .{});
286291 applyIniSoundContainerRules (ini_sound_container_rules , & file_tree );
287292
293+ const ini_deinlining_rules = try parseIniDeinliningRules (rules_folder_path , allocator );
294+ std .log .info ("Applying INI de-inlining rules...\n " , .{});
295+ try applyIniDeinliningRules (allocator , ini_deinlining_rules , & file_tree );
296+
288297 std .log .info ("Updating INI file tree...\n " , .{});
289298 try updateIniFileTree (& file_tree , allocator );
290299
@@ -1384,6 +1393,141 @@ fn applyIniSoundContainerRulesRecursivelyNode(node: *Node, property: []const u8)
13841393 }
13851394}
13861395
1396+ fn parseIniDeinliningRules (rules_folder_path : []const u8 , allocator : Allocator ) ! [][]const u8 {
1397+ const ini_deinlining_rules_path = try join (allocator , &.{ rules_folder_path , "ini_deinlining_rules.json" });
1398+ const text = try readFile (ini_deinlining_rules_path , allocator );
1399+ return try parseFromSliceLeaky ([][]const u8 , allocator , text , .{});
1400+ }
1401+
1402+ fn applyIniDeinliningRules (allocator : Allocator , ini_deinlining_rules : [][]const u8 , file_tree : * IniFolder ) ! void {
1403+ for (ini_deinlining_rules ) | property | {
1404+ try applyIniDeinliningRulesRecursivelyFolder (allocator , file_tree , property );
1405+ }
1406+ }
1407+
1408+ fn applyIniDeinliningRulesRecursivelyFolder (allocator : Allocator , file_tree : * IniFolder , property : []const u8 ) ! void {
1409+ for (file_tree .folders .items ) | * folder | {
1410+ try applyIniDeinliningRulesRecursivelyFolder (allocator , folder , property );
1411+ }
1412+
1413+ for (file_tree .files .items ) | * file | {
1414+ // This is kind of hideous. Loop through each item in the list.
1415+ var i : usize = 0 ;
1416+ while (i < file .ast .items .len ) {
1417+ const node : * Node = & file .ast .items [i ];
1418+ i = i + 1 ;
1419+
1420+ // Instead of looping over all nodes, loop over the children of each node.
1421+ // Why necessary? Because some unused inis use the word "Particle" as a root property.
1422+ for (node .children .items ) | * child | {
1423+ // Deinlining a definition into the file will push the current object further forward.
1424+ const insertionApplied : bool = try applyIniDeinliningRulesRecursivelyNode (allocator , child , property , file , node .* );
1425+
1426+ // We want to read the object again, since we stopped after the deinline, but we also want to read the definition that was deinlined.
1427+ // So decrement i.
1428+ if (insertionApplied ) {
1429+ i = i - 1 ;
1430+ break ;
1431+ }
1432+ }
1433+ }
1434+ }
1435+ }
1436+
1437+ fn applyIniDeinliningRulesRecursivelyNode (allocator : Allocator , node : * Node , property : []const u8 , file : * IniFile , rootParent : Node ) ! bool {
1438+ // If this has a property, and:
1439+ if (node .property ) | node_property | {
1440+ // If it matches the property given, and:
1441+ if (strEql (node_property , property )) {
1442+ // If this node has children, then:
1443+ if (node .children .items .len > 0 ) {
1444+ // This is a currently inlined constant reference, so:
1445+
1446+ // Find if this is a copy and the name of it's copy reference, and if this is a preset and it's preset name.
1447+ var copyOfNameOptional : ? []const u8 = null ;
1448+ var presetNameOptional : ? []const u8 = null ;
1449+
1450+ for (node .children .items ) | * child | {
1451+ if (child .property ) | child_property | {
1452+ if (strEql (child_property , "CopyOf" )) {
1453+ copyOfNameOptional = child .value ;
1454+ } else if (strEql (child_property , "PresetName" )) {
1455+ presetNameOptional = child .value ;
1456+ }
1457+ }
1458+ }
1459+
1460+ // Class name is given, module name is assumed, and probably wrong, but might be conveniently specified already.
1461+ const className = node .value orelse "None" ;
1462+ var moduleName : []const u8 = "Base.rte" ;
1463+
1464+ // Find out if it's conveniently specified.
1465+ if (copyOfNameOptional ) | nameWithPossibleModulePrefix | {
1466+ if (indexOf (u8 , nameWithPossibleModulePrefix , "/" )) | modulePrefixEnd | {
1467+ moduleName = nameWithPossibleModulePrefix [0.. modulePrefixEnd ];
1468+ }
1469+ }
1470+
1471+ // Assume that nothing is invalidated.
1472+ var invalidationFlag = false ;
1473+ if (presetNameOptional ) | presetName | {
1474+ if (! strEql (rootParent .property .? , "AddLoadout" )) {
1475+ const duplicateNode = Node {
1476+ .property = "AddEffect" ,
1477+ .value = node .value ,
1478+ .comments = try node .comments .clone (),
1479+ .children = try node .children .clone (),
1480+ };
1481+
1482+ const determinedIndex = index_of (Node , file .ast .items , rootParent ) orelse 0 ;
1483+ try file .ast .insert (determinedIndex , duplicateNode );
1484+
1485+ invalidationFlag = true ;
1486+ }
1487+
1488+ node .value = try allocPrint (allocator , "{s}/{s}/{s}" , .{ className , moduleName , presetName });
1489+ } else if (copyOfNameOptional ) | copyOfName | {
1490+ node .value = try allocPrint (allocator , "{s}/{s}/{s}" , .{ className , moduleName , copyOfName });
1491+ } else {
1492+ node .value = "None" ;
1493+ }
1494+
1495+ // If comments or children are needed, they belong to the duplicate, in it's definition.
1496+ node .children .clearRetainingCapacity ();
1497+ node .comments .clearRetainingCapacity ();
1498+
1499+ // This was, in any case, a constant reference, it shouldn't have children, not here.
1500+ // Return, and clarify to caller whether we had to deinline anything.
1501+ return invalidationFlag ;
1502+ }
1503+ }
1504+ }
1505+
1506+ // Since this isn't a malformed constant reference, check the same for this' children.
1507+ for (node .children .items ) | * child | {
1508+ const sequenceInvalidated = try applyIniDeinliningRulesRecursivelyNode (allocator , child , property , file , rootParent );
1509+
1510+ // We have discovered an inline definition, and corrected it, which we must immediately signal to the caller.
1511+ if (sequenceInvalidated ) {
1512+ return true ;
1513+ }
1514+ }
1515+
1516+ // This is not a malformed constant reference, neither does it contain any.
1517+ return false ;
1518+ }
1519+
1520+ // Stolen off stack exchange, surprised the std library hasn't been updated to remedy the specific problem solved by this
1521+ fn index_of (comptime T : type , slice : []const T , value : T ) ? usize {
1522+ for (slice , 0.. ) | element , index | {
1523+ if (std .meta .eql (value , element )) {
1524+ return index ;
1525+ }
1526+ } else {
1527+ return null ;
1528+ }
1529+ }
1530+
13871531fn updateIniFileTree (file_tree : * IniFolder , allocator : Allocator ) ! void {
13881532 try applyOnNodesAlloc (addGetsHitByMosWhenHeldToShields , file_tree , allocator );
13891533 try applyOnNodesAlloc (addGripStrength , file_tree , allocator );
@@ -2327,10 +2471,14 @@ fn writeAstRecursively(node: *Node, buffered_writer: anytype, depth: usize) !voi
23272471
23282472 if (node .property ) | property | {
23292473 try writeBuffered (buffered_writer , property );
2474+
2475+ // Named properties with values, and newline (for empty lines in multilinetext) require the equality symbol
2476+ if (node .value != null or strEql (property , "NewLine" )) {
2477+ try writeBuffered (buffered_writer , " = " );
2478+ }
23302479 }
23312480
23322481 if (node .value ) | value | {
2333- try writeBuffered (buffered_writer , " = " );
23342482 try writeBuffered (buffered_writer , value );
23352483 }
23362484
0 commit comments