Skip to content

Instantly share code, notes, and snippets.

@amis92
Last active November 2, 2019 13:54
Show Gist options
  • Save amis92/cdd68efeb436b7586b0d1aad7cfdd8ab to your computer and use it in GitHub Desktop.
Save amis92/cdd68efeb436b7586b0d1aad7cfdd8ab to your computer and use it in GitHub Desktop.
bsdata: check childId for being link id
#!/usr/bin/env dotnet-script
// Author: Amadeusz Sadowski
#r "nuget: morelinq, 3.2.0"
#r "nuget: WarHub.ArmouryModel.Workspaces.BattleScribe, [0.7.0-beta.3]"
using System.Collections.Immutable;
using System.Text.RegularExpressions;
using MoreLinq;
using WarHub.ArmouryModel.Source;
using WarHub.ArmouryModel.Source.BattleScribe;
using WarHub.ArmouryModel.Workspaces.BattleScribe;
public class Context
{
public ImmutableDictionary<string, SourceNode> Index { get; }
public CatalogueBaseNode CatalogueBase { get; }
public XmlWorkspace Workspace { get; }
private Context(ImmutableDictionary<string, SourceNode> index, CatalogueBaseNode catalogueBase, XmlWorkspace workspace)
{
Index = index;
CatalogueBase = catalogueBase;
Workspace = workspace;
}
public static Context Create(XmlDocument document) =>
Create((CatalogueBaseNode)document.GetRoot(), document.Workspace);
public static Context Create(CatalogueBaseNode catalogueBase, XmlWorkspace workspace)
{
var nonIndexableKinds =
ImmutableHashSet.Create(
SourceKind.CatalogueLinkList,
SourceKind.CategoryLinkList,
SourceKind.ConstraintList,
SourceKind.CostTypeList,
SourceKind.ProfileTypeList,
SourceKind.PublicationList);
var workspaceIndex =
workspace.GetDocuments(SourceKind.Catalogue, SourceKind.Gamesystem)
.Select(x => (CatalogueBaseNode)x.GetRoot())
.ToImmutableDictionary(x => x.Id);
var index =
new [] { catalogueBase }
.Concat(catalogueBase switch
{
CatalogueNode c => GetImports(c).Append(workspaceIndex[c.GamesystemId]),
_ => new CatalogueBaseNode[0]
})
.Distinct()
.SelectMany(x => x.DescendantsAndSelf(current => !nonIndexableKinds.Contains(current.Kind)))
.OfType<IIdentifiableNode>()
.Concat(workspaceIndex.Values)
.DistinctBy(x => x.Id)
.ToImmutableDictionary(x => x.Id, x => (SourceNode)x);
return new Context(index, catalogueBase, workspace);
IEnumerable<CatalogueNode> GetImports(CatalogueNode cat)
{
return
cat.CatalogueLinks
.Select(x => (CatalogueNode)workspaceIndex[x.TargetId])
.SelectMany(x => GetImports(x).Append(x));
}
}
}
public class ConditionChildTargetVisitor : SourceWalker
{
public delegate void Process(ConditionNode selector, EntryLinkNode link);
private readonly Process processAffected;
private readonly Action<SourceNode, string> processError;
private readonly Context context;
private readonly ImmutableHashSet<string> wellKnownIds =
ImmutableHashSet.Create(
"any",
"unit",
"model",
"upgrade");
public ConditionChildTargetVisitor(
Context context,
Process processAffected,
Action<SourceNode, string> processError = null)
{
this.processAffected = processAffected;
this.processError = processError;
this.context = context;
}
public override void VisitCondition(ConditionNode node)
{
if (wellKnownIds.Contains(node.ChildId))
{
return;
}
if (!context.Index.TryGetValue(node.ChildId, out var target))
{
processError(node, "ChildId target not found");
}
if (target is EntryLinkNode link)
{
processAffected(node, link);
}
}
}
public class InstanceOfConditionRewriter : SourceRewriter
{
public delegate ConditionNode Rewrite(ConditionNode condition, EntryLinkNode link);
private readonly Context context;
private readonly Rewrite rewrite;
private readonly ImmutableHashSet<string> wellKnownIds =
ImmutableHashSet.Create(
"any",
"unit",
"model",
"upgrade");
public InstanceOfConditionRewriter(Context context, Rewrite rewrite)
{
this.context = context;
this.rewrite = rewrite;
}
public override SourceNode VisitCondition(ConditionNode node)
{
if ((node.Type == ConditionKind.InstanceOf || node.Type == ConditionKind.NotInstanceOf)
&& !wellKnownIds.Contains(node.ChildId)
&& context.Index[node.ChildId] is EntryLinkNode link)
{
return rewrite(node, link);
}
return node;
}
}
string GetAncestryString(SourceNode node)
{
var ancestry =
(from anc in node.AncestorsAndSelf().Reverse()
select anc switch
{
INameableNode nameable => nameable.Name,
IIdentifiableNode ident => $"{anc.Kind} {ident.Id}",
{ Parent: {IsList: true} } => $"{anc.Kind}[{anc.IndexInParent + 1}]",
{ Parent: { } } => $"{anc.GetChildInfoFromParent().Name}",
_ => anc.Kind.ToString()
});
return string.Join(" -> ", ancestry);
}
var anchorRegex = new Regex(@"[^a-z0-9\-]+");
string GetAnchorString(string source)
{
var stg1 = source.ToLowerInvariant().Replace(" ", "-");
return anchorRegex.Replace(stg1, "");
}
void FindIssues(XmlDocument file)
{
var context = Context.Create(file);
var visitor = new ConditionChildTargetVisitor(context, (condition, link) =>
{
if (condition.Type == ConditionKind.InstanceOf || condition.Type == ConditionKind.NotInstanceOf)
{
i++;
Write("+ ");
WriteLine(GetAncestryString(condition));
}
}, (node, error) =>
{
WriteLine($"> **{error}** {GetAncestryString(node)}");
});
context.CatalogueBase.Accept(visitor);
}
void FixIssues(XmlDocument file)
{
var context = Context.Create(file);
var visitor = new InstanceOfConditionRewriter(context, (condition, link) =>
{
i++;
Write("+ ");
WriteLine(GetAncestryString(condition));
if (!context.Index.TryGetValue(link.TargetId, out var target))
{
WriteLine($" - failed to resolve child's target (link ID: {link.TargetId})");
return condition;
}
if (!(target is SelectionEntryBaseNode targetEntry))
{
WriteLine($" - child's target is not a SelectionEntry(Group) (link ID: {link.TargetId})");
return condition;
}
WriteLine($" - replaced `childId` of {condition.ChildId} (link) with {link.TargetId} (_{targetEntry.Name}_)");
return condition.WithChildId(link.TargetId);
});
var rewritten = (CatalogueBaseNode)context.CatalogueBase.Accept(visitor);
using var fileStream = File.OpenWrite(file.Filepath);
rewritten.Serialize(fileStream);
}
Action<XmlDocument> operation = FindIssues;
if (Args.Any(x => x == "--fix"))
{
operation = FixIssues;
}
var results = new Dictionary<string, int>();
var i = 0;
var ws = XmlWorkspace.CreateFromDirectory(".");
var files = Args.Where(x => !x.StartsWith("-")) is var paths && paths.Any() ?
(from path in paths
let expPath = Path.GetFullPath(path)
select ws.Documents.Single(df => string.Equals(expPath, df.Filepath, StringComparison.OrdinalIgnoreCase)))
: ws.Documents.Where(df => df.Kind == XmlDocumentKind.Catalogue || df.Kind == XmlDocumentKind.Gamesystem);
foreach (var file in files)
{
WriteLine("# " + file.Name);
WriteLine($"[{file.Name}]:#{GetAnchorString(file.Name)}");
i = 0;
operation(file);
WriteLine();
WriteLine($"_Count: {i}_");
WriteLine();
WriteLine();
results[file.Name] = i;
}
void PrintSummary()
{
var tableRows = new[]
{
("File", "Count"),
("-", "-:"),
}
.Concat(
results
.Where(x => x.Value > 0)
.Select(x => ($"[{x.Key}]", x.Value.ToString())))
.Append(("**Total**", results.Values.Sum().ToString()))
.ToList();
var padding = new[]
{
tableRows.Max(x => x.Item1.Length),
tableRows.Max(x => x.Item2.Length),
};
WriteLine();
WriteLine("---");
WriteLine();
WriteLine("# Summary");
WriteLine();
foreach (var (c1, c2) in tableRows)
{
WriteLine($"| {c1.PadRight(padding[0])} | {c2.ToString().PadLeft(padding[1])} |");
}
}
PrintSummary();

Aeldari - Craftworlds

  • Aeldari - Craftworlds -> SharedSelectionEntryGroups -> Autarch Weapons (Index) -> EntryLinks -> Laser Lance -> Modifiers -> Modifier[1] -> Conditions -> Condition[1]
    • replaced childId of 4544-0498-7c31-519d (link) with 7a74-f56b-28ad-cd55 (Autarch Skyrunner)

Count: 1

Aeldari - Drukhari

  • Aeldari - Drukhari -> SharedSelectionEntryGroups -> Artefacts of Cruelty -> SelectionEntries -> The Djin Blade -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of 9cf3-09b4-05fe-4ec9 (link) with 3ea9-f42c-d816-b91a (Archon)
  • Aeldari - Drukhari -> SharedSelectionEntryGroups -> Artefacts of Cruelty -> SelectionEntries -> The Flensing Blade -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of b9b4-b3d6-589c-b694 (link) with bcaa-5a3b-de61-6f41 (Haemonculus)

Count: 2

Aeldari - FW Corsairs

Count: 0

Aeldari - Harlequins

Count: 0

Aeldari - Ynnari

Count: 0

Chaos - Chaos Space Marines

Count: 0

Chaos - Daemons

Count: 0

Chaos - Dark Mechanicum

Count: 0

Chaos - Death Guard

Count: 0

Chaos - FW Heretic Astartes

Count: 0

Chaos - FW Renegade and Heretics

Count: 0

Chaos - Gellerpox Infected

Count: 0

Chaos - Questor Traitoris

Count: 0

Chaos - Servants of the Abyss

Count: 0

Chaos - Thousand Sons

Count: 0

Chaos - Titanicus Traitoris

Count: 0

Fallen

Count: 0

Imperium - Adeptus Astra Telepathica

Count: 0

Imperium - Adeptus Custodes

Count: 0

Imperium - Adeptus Mechanicus

Count: 0

Imperium - Adeptus Ministorum

Count: 0

Imperium - Adeptus Titanicus

Count: 0

Imperium - Astra Militarum

Count: 0

Imperium - Blackstone Fortress

Count: 0

Imperium - Blood Angels

Count: 0

Imperium - Dark Angels

  • Imperium - Dark Angels -> SharedSelectionEntries -> Space Marine Sergeant -> InfoLinks -> Signum -> Modifiers -> Modifier[1] -> Conditions -> Condition[1]
    • replaced childId of 2f9d-9cfa-2d72-dbfe (link) with a648-ad2f-fbdf-b180 (Devastator Squad)
  • Imperium - Dark Angels -> SharedSelectionEntries -> Black Knight Bike -> SelectionEntryGroups -> Armament -> SelectionEntries -> Ravenwing grenade launcher -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of f898-37e9-0ce3-1dbf (link) with 93f0-5272-7b0c-f986 (Ravenwing Apothecary)
  • Imperium - Dark Angels -> SharedSelectionEntryGroups -> Warlord Traits -> SelectionEntryGroups -> Warlord Traits (Dark Angels) -> SelectionEntries -> 3. Brilliant Strategist -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of 0af4-8fb6-5a9d-e965 (link) with e178-fe8e-6928-48c2 (Azrael)
  • Imperium - Dark Angels -> SharedSelectionEntryGroups -> Warlord Traits -> SelectionEntryGroups -> Warlord Traits (Dark Angels) -> SelectionEntries -> 4. Huntsman -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of 153c-2250-d024-389d (link) with 7d3f-a10f-0a57-d488 (Belial)
  • Imperium - Dark Angels -> SharedSelectionEntryGroups -> Warlord Traits -> SelectionEntryGroups -> Warlord Traits (Dark Angels) -> SelectionEntries -> 5. Master of Maneuvre -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of d7fb-1ed0-970a-58d0 (link) with 1eca-e59b-cd4e-d3d6 (Sammael in Sableclaw)
  • Imperium - Dark Angels -> SharedSelectionEntryGroups -> Warlord Traits -> SelectionEntryGroups -> Warlord Traits (Dark Angels) -> SelectionEntries -> 5. Master of Maneuvre -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> ConditionGroups -> ConditionGroup[2] -> Conditions -> Condition[1]
    • replaced childId of c086-ffb6-0621-accb (link) with 28b1-58d0-9871-9f8a (Sammael on Corvex)
  • Imperium - Dark Angels -> SharedSelectionEntryGroups -> Power Sword -> EntryLinks -> Monster Slayer of Caliban -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[2]
    • replaced childId of e46d-f194-7fae-7c04 (link) with b138-43e3-b9c2-6daa (Ravenwing Talonmaster)

Count: 7

Imperium - Deathwatch

  • Imperium - Deathwatch -> SharedSelectionEntries -> Aggressor -> Profiles -> Relentless Advance -> Modifiers -> Modifier[1] -> Conditions -> Condition[1]
    • replaced childId of 53bc-077a-de73-6611 (link) with 2567-072d-fea5-7e3e (Intercessors)
  • Imperium - Deathwatch -> SharedSelectionEntries -> Inceptor -> Profiles -> Inceptor Strike -> Modifiers -> Modifier[1] -> Conditions -> Condition[1]
    • replaced childId of 53bc-077a-de73-6611 (link) with 2567-072d-fea5-7e3e (Intercessors)

Count: 2

Imperium - Elucidian Starstriders

Count: 0

Imperium - FW Adeptus Astartes

Count: 0

Imperium - FW Death Korps of Krieg

Count: 0

Imperium - FW Elysians

Count: 0

Imperium - Grey Knights

  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Trait -> EntryLinks -> Warlord Traits (BRB) -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of e7b9-75a0-5700-eb0e (link) with 630a-1d50-0a37-a639 (Lord Kaldor Draigo)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Trait -> EntryLinks -> Warlord Traits (BRB) -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[4]
    • replaced childId of 92eb-aba6-a177-dad3 (link) with 30b4-cee3-1edf-7ee5 (Grand Master Voldus)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 1: Daemon Slayer -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of 92eb-aba6-a177-dad3 (link) with 30b4-cee3-1edf-7ee5 (Grand Master Voldus)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 2: Hammer of Righteousness -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of 92eb-aba6-a177-dad3 (link) with 30b4-cee3-1edf-7ee5 (Grand Master Voldus)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 2: Hammer of Righteousness -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[2]
    • replaced childId of e7b9-75a0-5700-eb0e (link) with 630a-1d50-0a37-a639 (Lord Kaldor Draigo)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 3: Unyielding Anvil -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of 92eb-aba6-a177-dad3 (link) with 30b4-cee3-1edf-7ee5 (Grand Master Voldus)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 3: Unyielding Anvil -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[2]
    • replaced childId of e7b9-75a0-5700-eb0e (link) with 630a-1d50-0a37-a639 (Lord Kaldor Draigo)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 4: First to the Fray -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of 92eb-aba6-a177-dad3 (link) with 30b4-cee3-1edf-7ee5 (Grand Master Voldus)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 4: First to the Fray -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[2]
    • replaced childId of e7b9-75a0-5700-eb0e (link) with 630a-1d50-0a37-a639 (Lord Kaldor Draigo)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 5: Nemesis Lord -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of 92eb-aba6-a177-dad3 (link) with 30b4-cee3-1edf-7ee5 (Grand Master Voldus)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 5: Nemesis Lord -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[2]
    • replaced childId of e7b9-75a0-5700-eb0e (link) with 630a-1d50-0a37-a639 (Lord Kaldor Draigo)
  • Imperium - Grey Knights -> SharedSelectionEntryGroups -> Grey Knight Warlord Traits (Codex) -> SelectionEntries -> 6: Lore Master -> Modifiers -> Modifier[1] -> ConditionGroups -> ConditionGroup[1] -> Conditions -> Condition[1]
    • replaced childId of e7b9-75a0-5700-eb0e (link) with 630a-1d50-0a37-a639 (Lord Kaldor Draigo)

Count: 12

Imperium - Inquisition

Count: 0

Imperium - Legion of the Damned

Count: 0

Imperium - Officio Assassinorum

Count: 0

Imperium - Questor Imperialis

  • Imperium - Imperial Knights -> SharedSelectionEntries -> Warlord Trait: Fearsome Reputation -> Modifiers -> Modifier[1] -> Conditions -> Condition[1]
    • replaced childId of 6d58-8b70-8309-d28a (link) with 3a62-00de-b0b5-7cf4 (Canis Rex)

Count: 1

Imperium - Sisters of Silence

Count: 0

Imperium - Space Marines

Count: 0

Imperium - Space Wolves

Count: 0

Necrons

Count: 0

Orks

Count: 0

T'au Empire

  • T'au Empire -> SharedSelectionEntryGroups -> Support systems -> EntryLinks -> Shield generator -> Modifiers -> Modifier[2] -> Conditions -> Condition[1]
    • replaced childId of d499-1777-f570-3dd9 (link) with 725a-82bb-106e-d3d9 (XV104 Riptide Battlesuit)

Count: 1

Tyranids - Genestealer Cults

Count: 0

Tyranids

Count: 0

Unaligned - Monsters and Gribblies

Count: 0

Warhammer 40,000 8th Edition

Count: 0


Summary

File Count
Aeldari - Craftworlds 1
Aeldari - Drukhari 2
Imperium - Dark Angels 7
Imperium - Deathwatch 2
Imperium - Grey Knights 12
Imperium - Questor Imperialis 1
T'au Empire 1
Total 26
@amis92
Copy link
Author

amis92 commented Nov 2, 2019

Resulted in PR BSData/wh40k-9e#5918

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment