Skip to content

Instantly share code, notes, and snippets.

@MaxGuernseyIII
Last active March 19, 2020 01:50
Show Gist options
  • Save MaxGuernseyIII/1fdb9c9c691a326aeb5649eae894fbac to your computer and use it in GitHub Desktop.
Save MaxGuernseyIII/1fdb9c9c691a326aeb5649eae894fbac to your computer and use it in GitHub Desktop.
Using SVG shape specifications
// Copyright © Hexagon Software LLC. All rights reserved.
using EscapeTiles.Instrumentation;
namespace EscapeTiles.Utilities
{
sealed class CombineShapeSpecification : ShapeSpecification
{
readonly ShapeSpecification Left;
readonly ShapeSpecification Right;
public CombineShapeSpecification(ShapeSpecification Left, ShapeSpecification Right)
{
this.Left = Left;
this.Right = Right;
}
public void Validate(Rectangle Actual)
{
Left.Validate(Actual);
Right.Validate(Actual);
}
}
}
// Copyright © Hexagon Software LLC. All rights reserved.
using EscapeTiles.Instrumentation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace EscapeTiles.Utilities
{
sealed class ContainedByShapeSpecification : ShapeSpecification
{
readonly Rectangle MustBeOutside;
public ContainedByShapeSpecification(Rectangle MustBeOutside)
{
this.MustBeOutside = MustBeOutside;
}
public void Validate(Rectangle Actual)
{
Assert.IsTrue(MustBeOutside.ContainsCompletely(Actual), "Expected {0} to be contained by {1}", Actual, MustBeOutside);
}
}
}
// Copyright © Hexagon Software LLC. All rights reserved.
using EscapeTiles.Instrumentation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace EscapeTiles.Utilities
{
sealed class ContainsShapeSpecification : ShapeSpecification
{
readonly Rectangle MustBeInside;
public ContainsShapeSpecification(Rectangle MustBeInside)
{
this.MustBeInside = MustBeInside;
}
public void Validate(Rectangle Actual)
{
Assert.IsTrue(Actual.ContainsCompletely(MustBeInside), "Expected {0} to contain {1}", Actual, MustBeInside);
}
}
}
// Copyright © Hexagon Software LLC. All rights reserved.
using System;
using System.IO;
using EscapeTiles.Instrumentation;
using EscapeTiles.Utilities.Svg;
namespace EscapeTiles.Utilities
{
sealed class GenerateErrorSpecification : ShapeSpecification
{
readonly ShapeSpecification Underlying;
readonly ArtifactDestination Artifacts;
readonly SvgShape Origin;
public GenerateErrorSpecification(ShapeSpecification Underlying, ArtifactDestination Artifacts, SvgShape Origin)
{
this.Underlying = Underlying;
this.Artifacts = Artifacts;
this.Origin = Origin;
}
public void Validate(Rectangle Actual)
{
try
{
Underlying.Validate(Actual);
}
catch
{
var PeerInNewDocument = Origin.CloneOwnerDocumentAndFindPeer();
var SvgDocument = PeerInNewDocument.Document;
PeerInNewDocument.AddClass("failing-expected");
var NewRect = SvgDocument.AddRect();
NewRect.SetCorners(Actual.TopLeft, Actual.BottomRight);
NewRect.AddClass("failing-actual");
Artifacts.CreateArtifact($"failure-{Guid.NewGuid():n}.svg", FullPath =>
{
using var Writer = File.Create(FullPath);
SvgDocument.Document.Save(Writer);
});
throw;
}
}
}
}
// Copyright © Hexagon Software LLC. All rights reserved.
using EscapeTiles.Instrumentation;
namespace EscapeTiles.Utilities
{
sealed class NoSpecification : ShapeSpecification
{
public void Validate(Rectangle Actual)
{
}
}
}
// Copyright © Hexagon Software LLC. All rights reserved.
using EscapeTiles.Instrumentation;
using EscapeTiles.Utilities.Svg;
namespace EscapeTiles.Utilities
{
struct RectangleAssertion
{
public SvgShape Origin { get; }
public Rectangle Operand { get; }
public RectangleAssertion(SvgShape Origin, Rectangle Operand)
{
this.Origin = Origin;
this.Operand = Operand;
}
}
}
// Copyright © Hexagon Software LLC. All rights reserved.
using EscapeTiles.Instrumentation;
namespace EscapeTiles.Utilities
{
sealed class ScaleShapeSpecification : ShapeSpecification
{
readonly Vector Scale;
readonly ShapeSpecification Underlying;
internal ScaleShapeSpecification(ShapeSpecification Underlying, Vector Scale)
{
this.Underlying = Underlying;
this.Scale = Scale;
}
public void Validate(Rectangle Actual)
{
Underlying.Validate(Actual.ScaleBy(Scale));
}
}
}
// Copyright © Hexagon Software LLC. All rights reserved.
using EscapeTiles.Instrumentation;
namespace EscapeTiles.Utilities
{
public interface ShapeSpecification
{
void Validate(Rectangle Actual);
}
}
// Copyright © Hexagon Software LLC. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using EscapeTiles.Instrumentation;
using EscapeTiles.Utilities.Svg;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace EscapeTiles.Utilities
{
sealed class ShapeSpecifications
{
readonly ArtifactDestination Artifacts;
readonly (float BoxWidth, float BoxHeight) Scale;
readonly SvgDocument SvgDocument;
ShapeSpecifications(XDocument Document, ArtifactDestination Artifacts)
{
SvgDocument = new SvgDocument(Document);
this.Artifacts = Artifacts;
var (X, Y, Width, Height) = SvgDocument.ViewBox;
Assert.AreEqual(0, X);
Assert.AreEqual(0, Y);
Scale = (Width, Height);
}
internal static Func<Stream, ShapeSpecifications> Reconstitute(ArtifactDestination Artifacts)
{
return From => new ShapeSpecifications(XDocument.Load(From), Artifacts);
}
internal ShapeSpecification GetViewportRequirement(string Key)
{
var Rectangles = FindSelectedRectanglesInDocument(Key);
var Result = ConvertRectanglesToShapeSpecification(Rectangles);
if (Result is NoSpecification)
Assert.Fail($"No valid rules found for key '{Key}'");
return ApplyScale(Result);
}
ShapeSpecification ApplyScale(ShapeSpecification Result) => new ScaleShapeSpecification(Result, Scale);
ShapeSpecification ConvertRectanglesToShapeSpecification(IEnumerable<RectangleSpecification> Rectangles)
{
ShapeSpecification Result = new NoSpecification();
foreach (var RectangleRule in Rectangles)
{
var Next = ConvertRectangleToSpecification(RectangleRule.Origin.Id, RectangleRule.Operand);
if (Next == null)
continue;
Next = new AddContextSpecification(
new GenerateErrorSpecification(Next, Artifacts, RectangleRule.Origin), RectangleRule.Origin.Id);
Result = new CombineShapeSpecification(Result, Next);
}
return Result;
}
IEnumerable<RectangleSpecification> FindSelectedRectanglesInDocument(string Key)
{
var SourceRectangles = SvgDocument.Rects.Select(ToRectangleRule)
.Concat(SvgDocument.Polygons.Select(ToRectangleRule))
.Concat(SvgDocument.Polylines.Select(ToRectangleRule));
return SourceRectangles.Where(R => Regex.IsMatch(R.Origin.Id, $@"^{Key}(?:\.[^.]*)*$", RegexOptions.IgnoreCase));
}
static ShapeSpecification ConvertRectangleToSpecification(string Id, Rectangle ExpectedViewportRectangle)
{
Id = Id.Trim();
if (IsOfType("inner"))
return new ContainsShapeSpecification(ExpectedViewportRectangle);
if (IsOfType("outer"))
return new ContainedByShapeSpecification(ExpectedViewportRectangle);
return null;
bool IsOfType(string Indicator)
{
return Regex.IsMatch(Id, $@"^(?:[^.]*\.)*{Indicator}$", RegexOptions.IgnoreCase);
}
}
static RectangleSpecification ToRectangleRule(SvgPolygon Polygon)
{
var Points = Polygon.Points;
var Min = Points[0];
var Max = Points[2];
return new RectangleSpecification(Polygon, Rectangle.ScreenAligned(Min, Max));
}
static RectangleSpecification ToRectangleRule(SvgRect Rect) =>
new RectangleSpecification(Rect, Rectangle.ScreenAligned(Rect.Min, Rect.Max));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment