Skip to content

Instantly share code, notes, and snippets.

@amis92
Last active October 6, 2020 17:40
Show Gist options
  • Save amis92/760757b559e4b070d6c4b037192637cd to your computer and use it in GitHub Desktop.
Save amis92/760757b559e4b070d6c4b037192637cd to your computer and use it in GitHub Desktop.
BSData duplicate ID analysis script
#!/usr/bin/env dotnet-script
#r "nuget: System.Linq.Async, 4.1.1"
#r "nuget: Optional, 4.0.0"
#r "nuget: WarHub.ArmouryModel.Workspaces.BattleScribe, 0.11.0"
#r "./script/bin/netcoreapp3.1/WarHub.GodMode.SourceAnalysis.dll"
using WarHub.ArmouryModel.ProjectModel;
using WarHub.ArmouryModel.Source;
using WarHub.ArmouryModel.Source.BattleScribe;
using WarHub.ArmouryModel.Workspaces.BattleScribe;
using WarHub.GodMode.SourceAnalysis;
if (Args.Count < 2)
throw new ArgumentException("You need to provide at least 2 arguments: base and target catalogue names.");
var baseName = Args[0];
var targetName = Args[1];
WriteLine($"Analyzing '{targetName}' (target) based on '{baseName}' (base).");
WriteLine("Starting. Loading workspace...");
var workspace = XmlWorkspace.CreateFromDirectory(".");
WriteLine("Creating analysis context...");
var ctx = (await GamesystemContext.CreateAsync(workspace)).Single();
var baseCatalogue = ctx.Catalogues.SingleOrDefault(x => x.Name == baseName)
?? throw new ArgumentException($"Catalogue '{baseName}' not found");
var targetCatalogue = ctx.Catalogues.Single(x => x.Name == targetName)
?? throw new ArgumentException($"Catalogue '{targetName}' not found");
if (ctx.Diagnostics.Length > 0)
Print(ctx.Diagnostics);
WriteLine("Indexing catalogues...");
Dictionary<string, List<SourceNode>> MapById(SourceNode root)
{
return root
.Descendants()
.Where(x => !x.IsList && x is IIdentifiableNode)
.GroupBy(x => ((IIdentifiableNode)x).Id)
.ToDictionary(x => x.Key, x => x.ToList());
}
var baseMap = MapById(baseCatalogue);
var targetMap = MapById(targetCatalogue);
bool internalDuplicates = false;
if (baseMap.Values.Any(x => x.Count > 1))
{
internalDuplicates = true;
WriteLine("Duplicate IDs found in base:");
foreach (var list in baseMap.Values.Where(x => x.Count > 0))
{
WriteLine("- ID: " + (list[0] as IIdentifiableNode).Id);
Print(list);
}
}
if (targetMap.Values.Any(x => x.Count > 1))
{
internalDuplicates = true;
WriteLine("Duplicate IDs found in target:");
foreach (var list in targetMap.Values.Where(x => x.Count > 0))
{
WriteLine("- ID: " + (list[0] as IIdentifiableNode).Id);
Print(list);
}
}
if (internalDuplicates)
{
WriteLine("Please fix duplicate IDs within catalogues first.");
return 0;
}
WriteLine("Indexed.");
WriteLine("Finding duplicate IDs across catalogues...");
string GetAncestryString(SourceNode node)
{
var ancestry =
(from anc in node.AncestorsAndSelf().Reverse().Skip(1)
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);
}
foreach (var key in baseMap.Keys)
{
if (targetMap.ContainsKey(key))
{
WriteLine("### Duplicate key " + key);
WriteLine("* Base: " + GetAncestryString(baseMap[key].Single()));
WriteLine("* Trgt: " + GetAncestryString(targetMap[key].Single()));
}
}
if (Args.Count > 2 && Args[2] == "dedupe")
{
WriteLine("Deduplication (removing target duplicates)...");
}
else
{
WriteLine("Done.");
return 0;
}
internal class LambdaRewriter : SourceRewriter
{
private readonly Func<SourceNode, SourceNode> selector;
public LambdaRewriter(Func<SourceNode, SourceNode> selector)
{
this.selector = selector;
}
public static TNode Visit<TNode>(TNode node, Func<SourceNode, SourceNode> selector)
where TNode : SourceNode
{
var visitor = new LambdaRewriter(selector);
return (TNode)visitor.Visit(node);
}
public override SourceNode Visit(SourceNode node)
{
return base.Visit(selector(node))!;
}
}
var targetDocument = await workspace.Documents
.ToAsyncEnumerable()
.WhereAwait(async x => (await x.GetRootAsync()) == targetCatalogue)
.SingleAsync();
var nodesToRemove = baseMap.Keys
.Select(key =>
{
return targetMap.TryGetValue(key, out var nodes) ? nodes.Single() : null;
})
.Where(x => x != null)
.ToHashSet();
foreach (var node in nodesToRemove)
{
WriteLine("### Removing node for duplicate key " + ((IIdentifiableNode)node).Id);
WriteLine("* Target node removed: " + GetAncestryString(node));
}
var strippedCatalogue = LambdaRewriter.Visit(targetCatalogue, x => nodesToRemove.Contains(x) ? null : x);
WriteLine("Saving target catalogue after nodes removed...");
using (var stream = File.Open(targetDocument.Filepath, FileMode.Create))
{
strippedCatalogue.Serialize(stream);
}
WriteLine("Done.");
return 0;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment