diff --git a/ObjectPrinting/ObjectExtensions.cs b/ObjectPrinting/ObjectExtensions.cs new file mode 100644 index 000000000..429062fc3 --- /dev/null +++ b/ObjectPrinting/ObjectExtensions.cs @@ -0,0 +1,13 @@ +using System; + +namespace ObjectPrinting; + +public static class ObjectExtensions +{ + public static string PrintToString(this T obj, Func, PrintingConfig> config) + { + var printingConfig = ObjectPrinter.For(); + printingConfig = config(printingConfig); + return printingConfig.PrintToString(obj); + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c32..5123ed02b 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,16 @@ +using System; +using System.Collections.Generic; + namespace ObjectPrinting { public class ObjectPrinter { + public static PrintingConfig For() { return new PrintingConfig(); } + + } } \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index c5db392ff..de324a29c 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -5,8 +5,10 @@ + - + + \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..522d3f00c 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,11 +1,62 @@ using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Text; +using ObjectPrinting; namespace ObjectPrinting { public class PrintingConfig { + private HashSet excludingTypes = new(); + private HashSet excludingProperties = new(); + private readonly Dictionary> typeSerializers = new(); + private readonly Dictionary> propertiesSerializers = new(); + + + public PropertyPrintingConfig ConfigureProperty(Expression> propertySelector) + { + var propertyName = ((MemberExpression)propertySelector.Body).Member.Name; + return new PropertyPrintingConfig(this, propertyName); + } + + public PrintingConfig Exclude() + { + excludingTypes.Add(typeof(T)); + return this; + } + + public PrintingConfig Exclude(Expression> propertySelector) + { + var propertyName = ((MemberExpression)propertySelector.Body).Member.Name; + excludingProperties.Add(propertyName); + return this; + } + + public TypePrintingConfig ConfigureType() + { + return new TypePrintingConfig(this); + } + + internal void AddPropertySerializer(string propertyName, Func serializer) + { + propertiesSerializers[propertyName] = obj => + { + var value = (TProp)obj; + + + + return serializer(value); + }; + } + + internal void AddTypeSerializer(Func serializer) + { + typeSerializers[typeof(TPropType)] = obj => serializer((TPropType)obj); + } + public string PrintToString(TOwner obj) { return PrintToString(obj, 0); @@ -13,29 +64,71 @@ public string PrintToString(TOwner obj) private string PrintToString(object obj, int nestingLevel) { - //TODO apply configurations if (obj == null) + { return "null" + Environment.NewLine; + } + + var objectType = obj.GetType(); + + if (typeSerializers.TryGetValue(objectType, out var serializer)) + { + return serializer(obj) + Environment.NewLine; + } + var finalTypes = new[] { typeof(int), typeof(double), typeof(float), typeof(string), typeof(DateTime), typeof(TimeSpan) }; - if (finalTypes.Contains(obj.GetType())) + if (finalTypes.Contains(objectType)) + { 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(objectType.Name); + foreach (var propertyInfo in objectType.GetProperties()) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + var propertyName = propertyInfo.Name; + var propertyType = propertyInfo.PropertyType; + + if (excludingProperties.Contains(propertyName) || excludingTypes.Contains(propertyType)) + continue; + + var propertyValue = propertyInfo.GetValue(obj); + string serializedValue = GetSerializedValue(propertyValue, propertyName, nestingLevel); + + sb.Append(identation + propertyName + " = " + serializedValue); } + return sb.ToString(); } + + + private string GetSerializedValue(object value, string propertyName, int nestingLevel) + { + + if (propertiesSerializers.TryGetValue(propertyName, out var propertySerializer)) + { + return propertySerializer(value) + Environment.NewLine; + } + + if (value != null && typeSerializers.TryGetValue(value.GetType(), out var typeSerializer)) + { + return typeSerializer(value) + Environment.NewLine; + } + + + return PrintToString(value, nestingLevel + 1); + } } + + + + + } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 000000000..570d3a02f --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,22 @@ +using System; + +namespace ObjectPrinting; + +public class PropertyPrintingConfig +{ + private readonly PrintingConfig parentConfig; + private readonly string propertyName; + + public PropertyPrintingConfig(PrintingConfig parentConfig, string propertyName) + { + this.parentConfig = parentConfig; + this.propertyName = propertyName; + } + + public PrintingConfig Using(Func serializer) + { + parentConfig.AddPropertySerializer(propertyName, serializer); + return parentConfig; + } + +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs new file mode 100644 index 000000000..6c41d122b --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace ObjectPrinting; + +public static class PropertyPrintingConfigExtensions +{ + public static PrintingConfig TrimTo(this PropertyPrintingConfig config, int maxLength) + { + Func serializer = str => + { + if (str == null) { return "null";} + + string result; + if (str.Length > maxLength) + { + result = str.Substring(0, maxLength); + } + else + { + result = str; + } + + return result; + + }; + + return config.Using(serializer); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445c..3a70406a0 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,320 @@ -using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using NUnit.Framework; +using ObjectPrinting.Solved; +using FluentAssertions; namespace ObjectPrinting.Tests { [TestFixture] public class ObjectPrinterAcceptanceTests { + + private Person testPerson; + + [SetUp] + public void Setup() + { + testPerson = new Person + { + Name = "Alexander", + Age = 25, + Height = 180.5, + Id = Guid.NewGuid() + }; + } + [Test] public void Demo() { - var person = new Person { Name = "Alex", Age = 19 }; + var person = new Person { Name = "Alex", Age = 19, Height = 160.1 }; - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter.For() //1. Исключить из сериализации свойства определенного типа + .Exclude() //2. Указать альтернативный способ сериализации для определенного типа + .ConfigureType().Using(i => i + " лет") //3. Для числовых типов указать культуру + .ConfigureType().Using(new CultureInfo("ru-RU")) //4. Настроить сериализацию конкретного свойства + .ConfigureProperty(p => p.Height).Using(i => i + " см") //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + .ConfigureProperty(p => p.Name).TrimTo(8) //6. Исключить из сериализации конкретного свойства - + .Exclude(p => p.Name); + + string s1 = printer.PrintToString(person); - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + string s2 = person.PrintToString(); + //8. ...с конфигурированием + string s3 = person.PrintToString(s => s.Exclude(p => p.Age)); + Console.WriteLine(s1); + Console.WriteLine(s2); + Console.WriteLine(s3); + + } + + [Test] + public void PrintToString_DefaultConfiguration_ReturnsAllProperties() + { + var result = testPerson.PrintToString(); + + result.Should().Contain("Name = Alexander") + .And.Contain("Age = 25") + .And.Contain("Height = 180.5") + .And.Contain("Id = "); + } + + [Test] + public void Exclude_ByType_ExcludesPropertiesOfSpecifiedType() + { + var printer = ObjectPrinter.For().Exclude(); + var result = printer.PrintToString(testPerson); + + result.Should().NotContain("Id") + .And.Contain("Name = Alexander") + .And.Contain("Age = 25") + .And.Contain("Height = 180.5"); + } + + [Test] + public void Exclude_ByProperty_ExcludesSpecificProperty() + { + var printer = ObjectPrinter.For().Exclude(p => p.Name); + var result = printer.PrintToString(testPerson); + + result.Should().NotContain("Name = Alexander") + .And.Contain("Age = 25") + .And.Contain("Height = 180.5"); + } + + [Test] + public void ConfigureType_UsingCustomSerialization_AppliesCustomFormat() + { + var printer = ObjectPrinter.For() + .ConfigureType().Using(i => i + " years"); + + var result = printer.PrintToString(testPerson); + + result.Should().Contain("Age = 25 years"); + } + + + [Test] + public void ConfigureProperty_UsingCustomSerialization_AppliesToSpecificProperty() + { + var printer = ObjectPrinter.For() + .ConfigureProperty(p => p.Height).Using(h => h + " cm"); + + var result = printer.PrintToString(testPerson); + + result.Should().Contain("Height = 180.5 cm"); + } + + [Test] + public void ConfigureProperty_TrimTo_TrimsStringProperty() + { + var printer = ObjectPrinter.For() + .ConfigureProperty(p => p.Name).TrimTo(4); + + var result = printer.PrintToString(testPerson); + + result.Should().Contain("Name = Alex"); + } + + [Test] + public void ConfigureProperty_TrimTo_WithLongString_TrimsCorrectly() + { + var person = new Person { Name = "VeryLongName", Age = 20, Height = 170 }; + var printer = ObjectPrinter.For() + .ConfigureProperty(p => p.Name).TrimTo(5); + + var result = printer.PrintToString(person); + + result.Should().Contain("Name = VeryL"); + } + + [Test] + public void PrintToString_WithConfiguration_AppliesConfiguration() + { + var result = testPerson.PrintToString(s => s.Exclude(p => p.Age)); + + result.Should().NotContain("Age = 25") + .And.Contain("Name = Alexander") + .And.Contain("Height = 180.5"); + } + + [Test] + public void MultipleConfigurations_WorkTogetherCorrectly() + { + var printer = ObjectPrinter.For() + .Exclude() + .ConfigureType().Using(i => i + " лет") + .ConfigureProperty(p => p.Height).Using(h => h + " см") + .ConfigureProperty(p => p.Name).TrimTo(3) + .Exclude(p => p.Age); + + var result = printer.PrintToString(testPerson); + + result.Should().NotContain("Id") + .And.NotContain("Age") + .And.Contain("Name = Ale") + .And.Contain("Height = 180.5 см"); + } + + [Test] + public void EmptyObject_ReturnsCorrectFormat() + { + var emptyPerson = new Person(); + var result = emptyPerson.PrintToString(); + + result.Should().Contain("Name = null") + .And.Contain("Age = 0") + .And.Contain("Height = 0"); + } + + [Test] + public void NullObject_ReturnsNullString() + { + Person nullPerson = null; + var result = nullPerson.PrintToString(); + result.Should().Be("null" + Environment.NewLine); + } + + [Test] + public void ConfigureType_ForStringType_WorksCorrectly() + { + var printer = ObjectPrinter.For() + .ConfigureType().Using(s => s?.ToUpper() ?? "NULL"); + + var result = printer.PrintToString(testPerson); + + result.Should().Contain("Name = ALEXANDER"); + } + + [Test] + public void Demo_WithAllFeatures_ProducesExpectedOutput() + { + var person = new Person { Name = "Alex", Age = 19, Height = 160.1 }; + + var printer = ObjectPrinter.For() + .Exclude() + .ConfigureType().Using(i => i + " лет") + .ConfigureType().Using(new CultureInfo("ru-RU")) + .ConfigureProperty(p => p.Height).Using(i => i + " см") + .ConfigureProperty(p => p.Name).TrimTo(3) + .Exclude(p => p.Name); + + var result = printer.PrintToString(person); + + result.Should().NotContain("Id") + .And.NotContain("Name = Ale") + .And.Contain("Age = 19 лет") + .And.Contain("Height = 160.1 см") + .And.NotContain("160,1"); } + + [Test] + public void ConfigureProperty_TrimTo_WithNullString_HandlesGracefully() + { + var person = new Person { Name = null, Age = 25 }; + var printer = ObjectPrinter.For() + .ConfigureProperty(p => p.Name).TrimTo(5); + + var result = printer.PrintToString(person); + + result.Should().Contain("Name = null"); + } + + [Test] + public void Exclude_MultipleTypes_ExcludesAllSpecifiedTypes() + { + var printer = ObjectPrinter.For() + .Exclude() + .Exclude() + .Exclude(); + + var result = printer.PrintToString(testPerson); + + result.Should().NotContain("Id") + .And.NotContain("Name") + .And.NotContain("Age") + .And.Contain("Height = 180.5"); + } + + [Test] + public void ConfigureType_And_ConfigureProperty_ForSameProperty_PropertyWins() + { + var printer = ObjectPrinter.For() + .ConfigureType().Using(d => d + " TYPE") + .ConfigureProperty(p => p.Height).Using(h => h + " PROPERTY"); + + var result = printer.PrintToString(testPerson); + + result.Should().Contain("Height = 180.5 PROPERTY") + .And.NotContain("TYPE"); + } + + [Test] + public void Exclude_AllProperties_ReturnsOnlyTypeName() + { + var printer = ObjectPrinter.For() + .Exclude(p => p.Id) + .Exclude(p => p.Name) + .Exclude(p => p.Height) + .Exclude(p => p.Age); + + var result = printer.PrintToString(testPerson); + + result.Trim().Should().Be("Person"); + } + + [Test] + public void ConfigureType_WithCultureInfo_AppliesCultureFormatting() + { + // Arrange + var person = new Person { Name = "Test", Age = 25, Height = 160.5 }; + var culture = new CultureInfo("ru-RU"); + + var printer = ObjectPrinter.For() + .ConfigureType().Using(culture); + + // Act + var result = printer.PrintToString(person); + + // Assert + result.Should().Contain("Height = 160,5"); + } + + [Test] + public void ConfigureType_WithDifferentCultures_FormatsNumbersCorrectly() + { + // Arrange + var person = new Person { Name = "Test", Age = 25, Height = 160.5 }; + var usCulture = new CultureInfo("en-US"); + var frCulture = new CultureInfo("fr-FR"); + + var printerUS = ObjectPrinter.For() + .ConfigureType().Using(usCulture); + + var printerFR = ObjectPrinter.For() + .ConfigureType().Using(frCulture); + + // Act + var resultUS = printerUS.PrintToString(person); + var resultFR = printerFR.PrintToString(person); + + // Assert + resultUS.Should().Contain("Height = 160.5"); + resultFR.Should().Contain("Height = 160,5"); + } + + } -} \ No newline at end of file + + +} diff --git a/ObjectPrinting/TypePrintingConfig.cs b/ObjectPrinting/TypePrintingConfig.cs new file mode 100644 index 000000000..50eb2467c --- /dev/null +++ b/ObjectPrinting/TypePrintingConfig.cs @@ -0,0 +1,41 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting +{ + public class TypePrintingConfig + { + private readonly PrintingConfig printingConfig; + + public TypePrintingConfig(PrintingConfig printingConfig) + { + this.printingConfig = printingConfig; + } + + public PrintingConfig Using(Func serializer) + { + printingConfig.AddTypeSerializer(serializer); + return printingConfig; + } + + public PrintingConfig Using(CultureInfo culture) + { + + if (typeof(TPropType) == typeof(double)) + { + printingConfig.AddTypeSerializer(d => d.ToString(culture)); + } + else if (typeof(TPropType) == typeof(float)) + { + printingConfig.AddTypeSerializer(f => f.ToString(culture)); + } + else if (typeof(TPropType) == typeof(int)) + { + printingConfig.AddTypeSerializer(i => i.ToString(culture)); + } + + + return printingConfig; + } + } +} \ No newline at end of file diff --git a/Samples/FluentMapper.Tests/FluentMapping.Tests.csproj b/Samples/FluentMapper.Tests/FluentMapping.Tests.csproj index 3828c5665..295951f1d 100644 --- a/Samples/FluentMapper.Tests/FluentMapping.Tests.csproj +++ b/Samples/FluentMapper.Tests/FluentMapping.Tests.csproj @@ -6,6 +6,7 @@ + diff --git a/Samples/FluentMapper/FluentMapping.csproj b/Samples/FluentMapper/FluentMapping.csproj index fa71b7ae6..995c6adc3 100644 --- a/Samples/FluentMapper/FluentMapping.csproj +++ b/Samples/FluentMapper/FluentMapping.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/Samples/Spectacle/Spectacle.csproj b/Samples/Spectacle/Spectacle.csproj index fa71b7ae6..995c6adc3 100644 --- a/Samples/Spectacle/Spectacle.csproj +++ b/Samples/Spectacle/Spectacle.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/fluent-api.sln.DotSettings b/fluent-api.sln.DotSettings index 135b83ecb..53fe49b2f 100644 --- a/fluent-api.sln.DotSettings +++ b/fluent-api.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016