diff --git a/StatePrinter.Tests/IntegrationTests/ObjectGraphsTest.cs b/StatePrinter.Tests/IntegrationTests/ObjectGraphsTest.cs index 5917ce6..c9aff46 100644 --- a/StatePrinter.Tests/IntegrationTests/ObjectGraphsTest.cs +++ b/StatePrinter.Tests/IntegrationTests/ObjectGraphsTest.cs @@ -17,6 +17,7 @@ // specific language governing permissions and limitations // under the License. +using System; using System.Collections.Generic; using NUnit.Framework; using StatePrinting.OutputFormatters; @@ -36,7 +37,12 @@ public void Setup() { printer = TestHelper.CreateTestPrinter(); - car = new Car(new SteeringWheel(new FoamGrip("Plastic"))) { Brand = "Toyota" }; + car = new Car(new SteeringWheel(new FoamGrip("Plastic"))) + { + Brand = "Toyota", + Wheels = new [] { new Wheel(), new Wheel(), new Wheel(), new Wheel() }, // testing populated collection + Passengers = new Passenger[0] // testing empty collection + }; course = new Course(); course.Members.Add(new Student("Stan", course)); @@ -45,7 +51,7 @@ public void Setup() } [Test] - public void ThreeLinkedGraph() + public void ThreeLinkedGraph_curly() { var expected = @"new Car() @@ -61,11 +67,75 @@ public void ThreeLinkedGraph() Weight = 525 } Brand = ""Toyota"" + Wheels = new Wheel[]() + { + [0] = new Wheel() + { + Diameter = 0 + } + [1] = new Wheel() + { + Diameter = 0 + } + [2] = new Wheel() + { + Diameter = 0 + } + [3] = new Wheel() + { + Diameter = 0 + } + } + Passengers = new Passenger[]() + { + } }"; printer.Assert.PrintEquals(expected, car); } + [Test] + public void ThreeLinkedGraph_strictCSharp() + { + printer.Configuration.OutputFormatter = new StrictCSharpStyle(printer.Configuration); + + var expected = + @"new Car() +{ + StereoAmplifiers = null, + steeringWheel = new SteeringWheel() + { + Size = 3, + Grip = new FoamGrip() + { + Material = ""Plastic"", + }, + Weight = 525, + }, + Brand = ""Toyota"", + Wheels = new Wheel[] + { + new Wheel() + { + Diameter = 0, + }, + new Wheel() + { + Diameter = 0, + }, + new Wheel() + { + Diameter = 0, + }, + new Wheel() + { + Diameter = 0, + }, + }, +}"; + printer.Assert.PrintEquals(expected, car); + } + [Test] public void ThreeLinkedGraph_json() { @@ -80,7 +150,22 @@ public void ThreeLinkedGraph_json() }, ""Weight"": 525 }, - ""Brand"": ""Toyota"" + ""Brand"": ""Toyota"", + ""Wheels"": [ + { + ""Diameter"": 0 + }, + { + ""Diameter"": 0 + }, + { + ""Diameter"": 0 + }, + { + ""Diameter"": 0 + } + ], + ""Passengers"": [] }"; printer.Assert.PrintEquals(expected, car); @@ -101,6 +186,22 @@ public void ThreeLinkedGraph_xmlstyle() 525 Toyota + + + 0 + + + 0 + + + 0 + + + 0 + + + + "; printer.Assert.PrintEquals(expected, car); @@ -129,7 +230,13 @@ public void CyclicGraph_curly() printer.Assert.PrintEquals(expected, course); } - + [Test] + public void CyclicGraph_strictCSharp() + { + printer.Configuration.OutputFormatter = new StrictCSharpStyle(printer.Configuration); + Assert.Throws(() => printer.PrintObject(course)); + } + [Test] public void CyclicGraph_Json() { @@ -307,18 +414,30 @@ public void CyclicGraph_xmlstyle() } } + internal class Passenger + { + } + #region car class Car { protected int? StereoAmplifiers = null; private SteeringWheel steeringWheel; public string Brand { get; set; } + public Wheel[] Wheels { get; set; } + public Passenger[] Passengers { get; set; } + public Car(SteeringWheel steeringWheel) { this.steeringWheel = steeringWheel; } } + internal class Wheel + { + public decimal Diameter { get; set; } + } + internal class SteeringWheel { internal int Size = 3; diff --git a/StatePrinter/OutputFormatters/StrictCSharpStyle.cs b/StatePrinter/OutputFormatters/StrictCSharpStyle.cs new file mode 100644 index 0000000..e72b93f --- /dev/null +++ b/StatePrinter/OutputFormatters/StrictCSharpStyle.cs @@ -0,0 +1,135 @@ +// Copyright 2014 Kasper B. Graversen +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using StatePrinting.Configurations; +using StatePrinting.Introspection; + +namespace StatePrinting.OutputFormatters +{ + /// + /// Formatting the tokens to a compilable C# representation. + /// + /// Circular refereces are not supported, as this would lead to infinte loops. + /// + public class StrictCSharpStyle : IOutputFormatter + { + Configuration configuration; + + public StrictCSharpStyle(Configuration configuration) + { + this.configuration = configuration; + } + + public string Print(List tokens) + { + var filter = new UnusedReferencesTokenFilter(); + var processed = filter.FilterUnusedReferences(tokens); + + return MakeString(processed); + } + + string MakeString(IList tokens) + { + var sb = new IndentingStringBuilder(configuration); + + for (var i = 0; i < tokens.Count; i++) + { + var token = tokens[i]; + MakeTokenString(token, sb); + } + + sb.TrimLast(); + var result = sb.ToString(); + + // remove final superfluous trailing comma - very hacky, could probably be done better + return result.Remove(result.Length - 1); + } + + void MakeTokenString(Token token, IndentingStringBuilder sb) + { + switch (token.Tokenkind) + { + case TokenType.StartScope: + case TokenType.StartList: + case TokenType.StartDict: + sb.AppendFormatLine("{{"); + sb.Indent(); + break; + + case TokenType.EndScope: + case TokenType.EndList: + case TokenType.EndDict: + sb.DeIndent(); + sb.AppendFormatLine("}},"); + break; + + case TokenType.SimpleFieldValue: + sb.AppendFormatLine("{0}", MakeFieldValue(token, token.Value)); + break; + + case TokenType.SeenBeforeWithReference: + throw CreateCyclicalObjectGraphException(); + + case TokenType.FieldnameWithTypeAndReference: + if (token.ReferenceNo != null) + { + throw CreateCyclicalObjectGraphException(); + } + + var fieldType = OutputFormatterHelpers.MakeReadable(token.FieldType); + + string value = string.Format("new {0}{1}", fieldType, token.FieldType.IsArray ? "" : "()"); + sb.AppendFormatLine("{0}", MakeFieldValue(token, value)); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + + string MakeFieldValue(Token token, string value, Token nextToken = null) + { + if (token.Field == null) + return string.Format("{0}", value); + + if (token.Field.Index.HasValue) + return string.Format("{0}", value); + + if (token.Field.Key != null) + return string.Format("[{0}] = {1}", token.Field.Key, value); + + if (!string.IsNullOrEmpty(token.Field.Name)) + { + var isEnumerable = token.FieldType != null && token.FieldType.IsAssignableFrom(typeof(IEnumerable)); + return string.Format("{0} = {1}{2}", token.Field.Name, value, isEnumerable ? "" : ","); + } + + return string.Format("{0}", value); + } + + NotSupportedException CreateCyclicalObjectGraphException() + { + return new NotSupportedException("Cyclical object graphs are not supported in StrictCSharp style."); + } + } +} diff --git a/StatePrinter/StatePrinter.csproj b/StatePrinter/StatePrinter.csproj index 4004082..3c6a831 100644 --- a/StatePrinter/StatePrinter.csproj +++ b/StatePrinter/StatePrinter.csproj @@ -80,6 +80,7 @@ +