diff --git a/ObjectPrinterTests/Collections.cs b/ObjectPrinterTests/Collections.cs new file mode 100644 index 00000000..0783d0b6 --- /dev/null +++ b/ObjectPrinterTests/Collections.cs @@ -0,0 +1,16 @@ +using System.Collections; +using ObjectPrinting.Tests; + +namespace ObjectPrinting +{ + public class Collections + { + public Dictionary Dictionary { set; get; } + public int[][] Array { set; get; } + public List List { set; get; } + + public List Persons { set; get; } + + public IEnumerable Enumerable; + } +} \ No newline at end of file diff --git a/ObjectPrinterTests/GlobalUsings.cs b/ObjectPrinterTests/GlobalUsings.cs new file mode 100644 index 00000000..cefced49 --- /dev/null +++ b/ObjectPrinterTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/ObjectPrinterTests/Kid.cs b/ObjectPrinterTests/Kid.cs new file mode 100644 index 00000000..8e9ac260 --- /dev/null +++ b/ObjectPrinterTests/Kid.cs @@ -0,0 +1,21 @@ +namespace ObjectPrinterTests +{ + public class Kid + { + public string Name { set; get; } + public Kid Parent; + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + public override bool Equals(object? obj) + { + if (obj?.GetType() != GetType()) return false; + var kid = (Kid)obj; + return Name == kid.Name; + } + } + +} \ No newline at end of file diff --git a/ObjectPrinterTests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinterTests/ObjectPrinterAcceptanceTests.cs new file mode 100644 index 00000000..b3c49615 --- /dev/null +++ b/ObjectPrinterTests/ObjectPrinterAcceptanceTests.cs @@ -0,0 +1,217 @@ +using System.Collections; +using System.Globalization; +using FluentAssertions; +using ObjectPrinting; +using ObjectPrinting.Tests; + +namespace ObjectPrinterTests; + +[TestFixture] +public class ObjectPrinterAcceptanceTests +{ + private readonly string newLine = Environment.NewLine; + + [Test] + public void ObjectPrinter_OnSubsequentPrints_ShouldNotContainCycle() + { + var person = new Person { Name = "Alex", Age = 19 }; + + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(person); + var result2 = printer.PrintToString(person); + result2.Should().NotContain("Cycle"); + } + + [Test] + public void ObjectPrinter_WhenExcludingIntType_ShouldPrintObjectWithoutIntPropertiesAndFields() + { + var person = new Person { Name = "Alex", Age = 19 }; + + var printer = ObjectPrinter.For(); + var result = printer.Excluding().PrintToString(person); + result.Should().NotContain(nameof(person.Age)); + result.Should().NotContain(nameof(person.Defects)); + } + + [Test] + public void ObjectPrinter_WhenExcludingMember_ShouldPrintObjectWithoutMember() + { + var person = new Person { Name = "Alex", Age = 19 }; + var printer = ObjectPrinter.For(); + var result = printer.Excluding(x => x.Age).PrintToString(person); + result.Should().NotContain(nameof(person.Age)); + } + + [Test] + public void ObjectPrinter_WhenPrintingPropertyUsingSomeConditional_ShouldPrintObjectWithModifiedProperty() + { + var person = new Person { Name = "Alex", Age = 15 }; + const string nameProperty = nameof(person.Age); + var printer = ObjectPrinter.For(); + var result = printer.Printing(x => x.Age).Using(x => nameProperty).PrintToString(person); + result.Should().Contain($"{nameProperty} = {nameProperty}"); + } + + [Test] + public void ObjectPrinter_WhenChangingPropertiesByType_ShouldBeObjectWithModifiedProperty() + { + var person = new Person { Name = "Alex", Age = 15, Height = 1.2 }; + + var printer = ObjectPrinter.For(); + var result = printer.Printing().Using(x => (x * x).ToString()).PrintToString(person); + result.Should().Contain($"{nameof(person.Height)} = {person.Height * person.Height}"); + } + + [Test] + public void PrintToString_WhenPrintAllMembersInObject_ShouldPrintAllPublicMembers() + { + var person = new Person { Name = "Alex", Age = 15, Height = 1.2 }; + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(person); + foreach (var property in person.GetType().GetProperties()) + { + result.Should().Contain($"{property.Name}"); + } + + foreach (var field in person.GetType().GetFields()) + { + result.Should().Contain($"{field.Name}"); + } + } + + [Test] + public void ObjectPrinter_WhenCultureIsSet_ShouldPrintPropertyWithCulture() + { + var person = new Person { Name = "Alex", Age = 15, Height = 2.4 }; + var culture = new CultureInfo("en-GB"); + var printer = ObjectPrinter.For(); + var result = printer.Printing(x => x.Defects).Using(culture).PrintToString(person); + result.Should().Contain($"{nameof(person.Defects)} = {person.Defects.ToString(culture)}"); + } + + [Test] + public void ObjectPrinter_WhenTrimmedStringProperties_ShouldPrintObjectWithTrimmedProperties() + { + const int maxLen = 1; + var person = new Person { Name = "Alex", Age = 15, Height = 2.4, Id = Guid.Empty }; + var printer = ObjectPrinter.For(); + printer.Printing().TrimmedToLength(maxLen); + var result = person.PrintToString(); + result.Should().Contain($"{nameof(person.Name)} = {person.Name[..maxLen]}"); + } + + [Test] + public void ObjectPrinter_WhenTrimmedStringPropertiesButCroppingLengthLess0_ShouldTrowArgumentException() + { + var person = new Person { Name = "Alex", Age = 15, Height = 2.4, Id = Guid.Empty }; + var printer = ObjectPrinter.For(); + var action = () => { printer.Printing().TrimmedToLength(-10).PrintToString(person); }; + action.Should().Throw("Error: The length of the truncated string cannot be negative"); + } + + [Test] + public void ObjectPrinter_WhenPropertyRefersItself_ShouldPrintObjectWithCycleProperty() + { + var kid = new Kid { Name = "Pasha" }; + var parent = new Kid { Name = "Lev" }; + kid.Parent = parent; + parent.Parent = kid; + + + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(kid); + result.Should().Contain(kid.GetType().Name); + result.Should().Contain($"\t{nameof(kid.Parent)} = {kid.GetType().Name}"); + result.Should().Contain($"\t\t{nameof(kid.Parent)} = (Cycle){kid.Parent.GetType().FullName}"); + } + + [Test] + public void ObjectPrinter_WhenPrintingDictionaryProperty_ShouldPrintObject() + { + var dictionary = new Dictionary + { + { 1, "hello" }, + { 2, "hell" }, + { 3, "hel" }, + { 4, "he" }, + { 5, "h" } + }; + var collections = new Collections(); + collections.Dictionary = new Dictionary(dictionary); + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(collections); + result.Should().Contain($"{nameof(collections.Dictionary)}"); + foreach (var value in dictionary) result.Should().Contain($"{value.Key}{newLine} : {value.Value}{newLine}"); + } + [Test] + public void ObjectPrinter_WhenPrintingIEnumerableProperty_ShouldPrintObject() + { + var collections = new Collections(); + collections.Enumerable = new Stack(new[] { 1, 2, 3, 4 }); + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(collections); + foreach (var value in collections.Enumerable) result.Should().Contain($"{value}{newLine}"); + } + + [Test] + public void ObjectPrinter_WhenThereIsObjectWithListProperty_ShouldPrintObject() + { + var collections = new Collections(); + collections.List = new List { 1, 2, 3 }; + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(collections); + foreach (var value in collections.List) result.Should().Contain($"{value}{newLine}"); + } + + [Test] + public void ObjectPrinter_WhenThereIsArrayGenericObjects_ShouldPrintObject() + { + var collections = new Collections(); + collections.Array = new[] { new[] { 1, 2, 3 } }; + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(collections); + result.Should().Contain($"{nameof(collections.Array)} = {newLine}"); + foreach (var list in collections.Array) + { + result.Should().Contain($"\t\t{list.GetType().Name}{newLine}"); + foreach (var value in list) result.Should().Contain($"\t\t\t{value}{newLine}"); + } + } + + [Test] + public void ObjectPrinter_WhenThereIsEnumerableTypeRefersItself_ShouldPrintObject() + { + var collections = new Collections(); + collections.List = new List(); + collections.List.Add(collections.List); + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(collections); + result.Should().Contain($"{nameof(collections.List)} = {newLine}"); + foreach (var list in collections.List) result.Should().Contain($"\t\t\t(Cycle){list.GetType().FullName}"); + } + + [Test] + public void ObjectPrinter_WhenPrintingSomeClassesInList_ShouldPrintObject() + { + var collections = new Collections(); + var child = new Person { Name = "Child" }; + collections.Persons = new List { new() { Name = "Lev" }, child, child }; + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(collections); + result.Should().Contain($"{nameof(collections.Persons)} = "); + foreach (var person in collections.Persons) + { + result.Should().Contain($"\t\t{person.GetType().Name}"); + result.Should().Contain($"\t\t\t{nameof(person.Name)} = {person.Name}{newLine}"); + result.Should().Contain($"\t\t\t{nameof(person.Age)} = {person.Age}{newLine}"); + } + } + [Test] + public void ObjectPrinter_WhenPrintingIdenticalPersons_ShouldPrintObjectWithoutCycle() + { + var person1 = new Kid() { Name = "Abobus" }; + person1.Parent = new Kid() { Name = "Abobus" }; + var result = person1.PrintToString(); + result.Should().NotContain("(Cycle)"); + } +} \ No newline at end of file diff --git a/ObjectPrinterTests/ObjectPrinterTests.csproj b/ObjectPrinterTests/ObjectPrinterTests.csproj new file mode 100644 index 00000000..f8a106a3 --- /dev/null +++ b/ObjectPrinterTests/ObjectPrinterTests.csproj @@ -0,0 +1,25 @@ + + + + net6.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/ObjectPrinterTests/Person.cs b/ObjectPrinterTests/Person.cs new file mode 100644 index 00000000..a5299b7b --- /dev/null +++ b/ObjectPrinterTests/Person.cs @@ -0,0 +1,11 @@ +namespace ObjectPrinting.Tests; + +public class Person +{ + public Guid Id { get; set; } = Guid.Empty; + public string Name { get; set; } + public double Height { get; set; } + public int Age { get; set; } + public int Defects = 45; + public string Text = "some text"; +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectExtensions.cs b/ObjectPrinting/ObjectExtensions.cs new file mode 100644 index 00000000..6879ab29 --- /dev/null +++ b/ObjectPrinting/ObjectExtensions.cs @@ -0,0 +1,9 @@ +namespace ObjectPrinting; + +public static class ObjectExtensions +{ + public static string PrintToString(this T obj) + { + return ObjectPrinter.For().PrintToString(obj); + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index 1c5eaf1c..74a93316 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -1,12 +1,13 @@  - 8 + latest netcoreapp3.1 false + diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..639def7b 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,228 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; -namespace ObjectPrinting +namespace ObjectPrinting; + +public class PrintingConfig { - public class PrintingConfig + private readonly List excludedTypes = new(); + private readonly List excludedProperties = new(); + private readonly Dictionary serializedByType = new(); + + private readonly Dictionary serializedByMemberInfo = + new(); + + private readonly HashSet openObjects = new(); + + public string PrintToString(TOwner obj) { - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0); - } + return PrintToString(obj, 0); + } + + public PropertyPrintingConfig Printing() + { + return new PropertyPrintingConfig(this, null); + } + + public PropertyPrintingConfig Printing( + Expression> memberSelector) + { + return new PropertyPrintingConfig(this, GetMemberInfo(memberSelector)); + } + + public PrintingConfig Excluding(Expression> memberSelector) + { + excludedProperties.Add(GetMemberInfo(memberSelector)); + return this; + } - private string PrintToString(object obj, int nestingLevel) + public PrintingConfig Excluding() + { + excludedTypes.Add(typeof(TMemberType)); + return this; + } + + private static MemberInfo GetMemberInfo(Expression> memberSelector) + { + if (memberSelector.Body is not MemberExpression memberExpression) + throw new ArgumentException($"Expression '{memberSelector}' refers to a method, not a property or field."); + + if (!IsPropertyOrFieldMember(memberExpression.Member)) + throw new ArgumentException($"Expression '{memberSelector}' refers to an unsupported member type."); + + return memberExpression.Member; + } + + private static bool IsFinalType(Type type) + { + return type == typeof(string) + || type.IsPrimitive + || typeof(IFormattable).IsAssignableFrom(type); + } + + private string PrintToString(object obj, int nestingLevel) + { + if (obj == null) + return "null" + Environment.NewLine; + + var type = obj.GetType(); + if (IsFinalType(type)) + return obj + Environment.NewLine; + + var sb = new StringBuilder(); + if (openObjects.Any(x => ReferenceEquals(x, obj))) + return sb.AppendLine("(Cycle)" + type.FullName).ToString(); + openObjects.Add(obj); + sb.AppendLine(type.Name); + if (IsDictionary(type)) return sb.Append(SerializeEnumerable(obj, nestingLevel)).ToString(); + + if (IsArrayOrList(type)) return sb.Append(SerializeEnumerable(obj, nestingLevel)).ToString(); + + sb.Append(PrintPropertiesAndFields(obj, nestingLevel, type)); + openObjects.Remove(obj); + return sb.ToString(); + } + + private static Type GetType(MemberInfo member) + { + if (!IsPropertyOrFieldMember(member)) + throw new ArgumentException($"Expression '{member}' refers to a method, not a property or field."); + return (member as FieldInfo)?.FieldType + ?? (member as PropertyInfo)?.PropertyType; + } + + private static object GetValue(MemberInfo member, object obj) + { + if (!IsPropertyOrFieldMember(member)) + throw new ArgumentException($"Expression '{member}' refers to a method, not a property or field."); + return (member as FieldInfo)?.GetValue(obj) + ?? (member as PropertyInfo)?.GetValue(obj); + } + private string PrintPropertiesAndFields(object obj, int nestingLevel, Type type) + { + var indentation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + foreach (var memberInfo in type.GetMembers().Where(prop =>IsPropertyOrFieldMember(prop) && !IsExcluded(prop))) { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; + if (TrySerializeMember(obj, memberInfo, GetType(memberInfo), out var serializedValue)) + { + sb.AppendLine($"{indentation}{memberInfo.Name} = {serializedValue}"); + continue; + } - var finalTypes = new[] + if (IsDictionary(GetType(memberInfo))) { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; - - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - var type = obj.GetType(); - sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) + sb.AppendLine(indentation + memberInfo.Name + " = "); + sb.Append(SerializeDictionary(GetValue(memberInfo, obj), nestingLevel + 1)); + } + else if (IsArrayOrList(GetType(memberInfo))) + { + sb.AppendLine(indentation + memberInfo.Name + " = "); + sb.Append(SerializeEnumerable(GetValue(memberInfo, obj), nestingLevel + 1)); + } + else { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + sb.Append( + $"{indentation}{memberInfo.Name} = " + + $"{PrintToString(GetValue(memberInfo, obj), nestingLevel + 1)}" + ); } - return sb.ToString(); } + + + return sb.ToString(); + } + + private static bool IsPropertyOrFieldMember(MemberInfo member) + { + return member.MemberType is MemberTypes.Field or MemberTypes.Property; + } + private bool TrySerializeMember(object obj, MemberInfo memberInfo, Type propertyType, + out string serializedValue) + { + serializedValue = null; + Delegate valueToUse = null; + if (serializedByMemberInfo.TryGetValue(memberInfo, out var propertyValue)) + valueToUse = propertyValue; + if (serializedByType.TryGetValue(propertyType, out var typeValue)) + valueToUse = typeValue; + + return valueToUse != null && TrySerializeValue(valueToUse, GetValue(memberInfo, obj), out serializedValue); + } + + private static bool TrySerializeValue(Delegate serializer, object value, out string serializedValue) + { + try + { + serializedValue = serializer.DynamicInvoke(value)?.ToString(); + return true; + } + catch + { + serializedValue = null; + return false; + } + } + + private static bool IsArrayOrList(Type type) + { + return typeof(IEnumerable).IsAssignableFrom(type) && !IsFinalType(type); + } + + private static bool IsDictionary(Type type) + { + return typeof(IDictionary).IsAssignableFrom(type); + } + + private string SerializeEnumerable(object obj, int nestingLevel) + { + var enumerable = (IEnumerable)obj; + var sb = new StringBuilder(); + var indentation = new string('\t', nestingLevel + 1); + if (enumerable == null) return $"{indentation}null" + Environment.NewLine; + foreach (var value in enumerable) + { + sb.Append(indentation); + sb.Append(PrintToString(value, nestingLevel + 1)); + } + + return sb.ToString(); + } + + private string SerializeDictionary(object obj, int nestingLevel) + { + var dictionary = (IDictionary)obj; + var sb = new StringBuilder(); + var indentation = new string('\t', nestingLevel + 1); + if (dictionary == null) return $"{indentation}null" + Environment.NewLine; + foreach (var keyVal in dictionary) + { + sb.Append(indentation); + var key = ((DictionaryEntry)keyVal).Key; + var value = ((DictionaryEntry)keyVal).Value; + sb.Append(PrintToString(key, nestingLevel) + " : " + PrintToString(value, nestingLevel + 1)); + } + + return sb.ToString(); + } + + private bool IsExcluded(MemberInfo memberInfo) + { + return excludedProperties.Contains(memberInfo) || excludedTypes.Contains(GetType(memberInfo)); + } + + public void AddSerializeMember(Func print, MemberInfo memberInfo) + { + serializedByMemberInfo.Add(memberInfo, print); + } + + public void AddSerializeByType(Type type, Func print) + { + serializedByType.Add(type, print); } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 00000000..2e7871c3 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,37 @@ +using System; +using System.Globalization; +using System.Reflection; + +namespace ObjectPrinting +{ + public class PropertyPrintingConfig + { + private readonly PrintingConfig printingConfig; + private readonly MemberInfo memberInfo; + + public PropertyPrintingConfig(PrintingConfig printingConfig, MemberInfo memberInfo) + { + this.printingConfig = printingConfig; + this.memberInfo = memberInfo; + } + + public PrintingConfig Using(Func print) + { + if (memberInfo != null) + { + printingConfig.AddSerializeMember(print, memberInfo); + } + else + { + printingConfig.AddSerializeByType(typeof(TMemberType), print); + } + + return printingConfig; + } + + public PrintingConfig Using(CultureInfo culture) + { + return Using(x =>((IConvertible)x).ToString(culture)); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs new file mode 100644 index 00000000..c77ec4a0 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,16 @@ +using System; + +namespace ObjectPrinting +{ + public static class PropertyPrintingConfigExtensions + { + public static PrintingConfig TrimmedToLength(this PropertyPrintingConfig propConfig, int maxLen) + { + if (maxLen < 0) + { + throw new ArgumentException("Error: The length of the truncated string cannot be negative"); + } + return propConfig.Using(x => x.Length > maxLen ? x[..maxLen] : x); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index 4c8b2445..00000000 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using NUnit.Framework; - -namespace ObjectPrinting.Tests -{ - [TestFixture] - public class ObjectPrinterAcceptanceTests - { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs deleted file mode 100644 index f9555955..00000000 --- a/ObjectPrinting/Tests/Person.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ObjectPrinting.Tests -{ - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } -} \ No newline at end of file diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9e..c8d7da41 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -5,13 +5,7 @@ VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrinting", "ObjectPrinting\ObjectPrinting.csproj", "{07B8C9B7-8289-46CB-9875-048A57758EEE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{6D308E4A-CEC7-4536-9B87-81CD337A87AD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping", "Samples\FluentMapper\FluentMapping.csproj", "{FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping.Tests", "Samples\FluentMapper.Tests\FluentMapping.Tests.csproj", "{8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrinterTests", "ObjectPrinterTests\ObjectPrinterTests.csproj", "{7ED413CE-935B-4048-B476-89F38345811F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -23,25 +17,14 @@ Global {07B8C9B7-8289-46CB-9875-048A57758EEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {07B8C9B7-8289-46CB-9875-048A57758EEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {07B8C9B7-8289-46CB-9875-048A57758EEE}.Release|Any CPU.Build.0 = Release|Any CPU - {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}.Release|Any CPU.Build.0 = Release|Any CPU - {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}.Release|Any CPU.Build.0 = Release|Any CPU - {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.Build.0 = Release|Any CPU + {7ED413CE-935B-4048-B476-89F38345811F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7ED413CE-935B-4048-B476-89F38345811F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7ED413CE-935B-4048-B476-89F38345811F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7ED413CE-935B-4048-B476-89F38345811F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} - {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} - {EFA9335C-411B-4597-B0B6-5438D1AE04C3} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} EndGlobalSection EndGlobal