Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Бабин Георгий #191

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions ObjectPrinting/Extensions/InnerPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Globalization;
using ObjectPrinting.InnerPrintingConfigs;

namespace ObjectPrinting.Extensions
{
public static class InnerPrintingConfigExtensions
{
public static PrintingConfig<TOwner> Using<TOwner>(this IInnerPrintingConfig<TOwner, double> config, CultureInfo culture)
{
return config.Using(x => x.ToString(culture));;
}

public static PrintingConfig<TOwner> Using<TOwner>(this IInnerPrintingConfig<TOwner, float> config, CultureInfo culture)
{
return config.Using(x => x.ToString(culture));;
}

public static PrintingConfig<TOwner> Using<TOwner>(this IInnerPrintingConfig<TOwner, DateTime> config, CultureInfo culture)
{
return config.Using(x => x.ToString(culture));;
}
}
}
17 changes: 17 additions & 0 deletions ObjectPrinting/Extensions/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace ObjectPrinting.Extensions
{
public static class ObjectExtensions
{
public static string PrintToString<T>(this T obj)
{
return ObjectPrinter.For<T>().PrintToString(obj);
}

public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> config)
{
return config(ObjectPrinter.For<T>()).PrintToString(obj);
}
}
}
12 changes: 12 additions & 0 deletions ObjectPrinting/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace ObjectPrinting.Extensions
{
public static class StringExtensions
{
internal static string Truncate(this string text, int maxLength, string truncationSuffix = "")
{
return text.Length > maxLength
? text[..maxLength] + truncationSuffix
: text;
}
}
}
18 changes: 18 additions & 0 deletions ObjectPrinting/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ObjectPrinting.PropertyOrField;

namespace ObjectPrinting.Extensions
{
public static class TypeExtensions
{
internal static IEnumerable<IPropertyOrField> GetFieldsAndProperties(this Type type, BindingFlags bindingAttr)
{
return type.GetFields(bindingAttr)
.Select(x => new PropertyOrField.PropertyOrField(x))
.Concat(type.GetProperties(bindingAttr).Select(x => new PropertyOrField.PropertyOrField(x)));
}
}
}
10 changes: 10 additions & 0 deletions ObjectPrinting/InnerPrintingConfigs/IInnerPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace ObjectPrinting.InnerPrintingConfigs
{
public interface IInnerPrintingConfig<TOwner, TType>
{
PrintingConfig<TOwner> Using(Func<TType, string> print);
PrintingConfig<TOwner> TrimmedToLength(int maxLen);
}
}
32 changes: 32 additions & 0 deletions ObjectPrinting/InnerPrintingConfigs/MemberPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Reflection;
using ObjectPrinting.Extensions;

namespace ObjectPrinting.InnerPrintingConfigs
{
public class MemberPrintingConfig<TOwner, TMemberType> : PrintingConfig<TOwner>, IInnerPrintingConfig<TOwner, TMemberType>
{
private readonly MemberInfo memberInfo;

internal MemberPrintingConfig(PrintingConfig<TOwner> parent, MemberInfo memberInfo) : base(parent)
{
this.memberInfo = memberInfo;
}

public PrintingConfig<TOwner> Using(Func<TMemberType, string> print)
{
MemberSerializers[memberInfo] = obj => print((TMemberType)obj);
return this;
}

public PrintingConfig<TOwner> TrimmedToLength(int maxLen)
{
var isSerialized = MemberSerializers.TryGetValue(memberInfo, out var prevSerializer);
MemberSerializers[memberInfo] = isSerialized
? obj => prevSerializer(obj).Truncate(maxLen)
: obj => obj.ToString().Truncate(maxLen);

return this;
}
}
}
26 changes: 26 additions & 0 deletions ObjectPrinting/InnerPrintingConfigs/TypePrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using ObjectPrinting.Extensions;

namespace ObjectPrinting.InnerPrintingConfigs
{
public class TypePrintingConfig<TOwner, TType> : PrintingConfig<TOwner>, IInnerPrintingConfig<TOwner, TType>
{
internal TypePrintingConfig(PrintingConfig<TOwner> parent) : base(parent)
{ }

public PrintingConfig<TOwner> Using(Func<TType, string> print)
{
TypeSerializers[typeof(TType)] = obj => print((TType)obj);
return this;
}

public PrintingConfig<TOwner> TrimmedToLength(int maxLen)
{
var isSerialized = TypeSerializers.TryGetValue(typeof(TType), out var prevSerializer);
TypeSerializers[typeof(TType)] = isSerialized
? obj => prevSerializer(obj).Truncate(maxLen)
: obj => obj.ToString().Truncate(maxLen);
return this;
}
}
}
6 changes: 3 additions & 3 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
namespace ObjectPrinting
{
public class ObjectPrinter
public static class ObjectPrinter
{
public static PrintingConfig<T> For<T>()
public static PrintingConfig<T> For<T>(int maxNestingLevel = 10)
{
return new PrintingConfig<T>();
return new PrintingConfig<T>(maxNestingLevel);
}
}
}
9 changes: 2 additions & 7 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LangVersion>8</LangVersion>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>9</LangVersion>
<TargetFramework>net6.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
</ItemGroup>

</Project>
149 changes: 132 additions & 17 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,156 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using ObjectPrinting.Extensions;
using ObjectPrinting.InnerPrintingConfigs;
using ObjectPrinting.PropertyOrField;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
{
private readonly Type[] finalTypes =
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan), typeof(Guid)
};
private readonly HashSet<Type> excludedTypes = new();
private readonly HashSet<MemberInfo> excludedMembers = new();
protected readonly Dictionary<Type, Func<object, string>> TypeSerializers = new();
protected readonly Dictionary<MemberInfo, Func<object, string>> MemberSerializers = new();
private int MaxNestingLevel { get; }

public PrintingConfig(int maxNestingLevel = 10)
{
MaxNestingLevel = maxNestingLevel;
}

protected PrintingConfig(PrintingConfig<TOwner> parent)
{
excludedTypes = new HashSet<Type>(parent.excludedTypes);
excludedMembers = new HashSet<MemberInfo>(parent.excludedMembers);
TypeSerializers = new Dictionary<Type, Func<object, string>>(parent.TypeSerializers);
MemberSerializers = new Dictionary<MemberInfo, Func<object, string>>(parent.MemberSerializers);
MaxNestingLevel = parent.MaxNestingLevel;
}

public IInnerPrintingConfig<TOwner, TType> Printing<TType>()
{
var config = new PrintingConfig<TOwner>(this);
return new TypePrintingConfig<TOwner, TType>(config);
}

public IInnerPrintingConfig<TOwner, TMemberType> Printing<TMemberType>(Expression<Func<TOwner, TMemberType>> memberSelector)
{
if (memberSelector.Body is not MemberExpression memberExpression)
throw new ArgumentException("memberSelector should me MemberExpression");
var memberInfo = memberExpression.Member;
if (memberInfo.DeclaringType != typeof(TOwner))
throw new ArgumentException($"You should access {typeof(TOwner)} property or field");

var config = new PrintingConfig<TOwner>(this);
return new MemberPrintingConfig<TOwner, TMemberType>(config, memberInfo);
}

public PrintingConfig<TOwner> Excluding<TMemberType>(Expression<Func<TOwner, TMemberType>> memberSelector)
{
if (memberSelector.Body is not MemberExpression memberExpression)
throw new ArgumentException("memberSelector should me MemberExpression");
var memberInfo = memberExpression.Member;
if (memberInfo.DeclaringType != typeof(TOwner))
throw new ArgumentException($"You should access {typeof(TOwner)} property or field");
var config = new PrintingConfig<TOwner>(this);
config.excludedMembers.Add(memberInfo);
return config;
}

public PrintingConfig<TOwner> Excluding<TType>()
{
var config = new PrintingConfig<TOwner>(this);
config.excludedTypes.Add(typeof(TType));
return config;
}

public string PrintToString(TOwner obj)
{
return PrintToString(obj, 0);
}

private string PrintToString(object obj, int nestingLevel)
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;

var finalTypes = new[]
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
};
if (finalTypes.Contains(obj.GetType()))
var type = obj.GetType();

if (TypeSerializers.TryGetValue(type, out var serializer))
return serializer(obj) + Environment.NewLine;
if (finalTypes.Contains(type) || nestingLevel == MaxNestingLevel)
return obj + Environment.NewLine;
if (obj is ICollection collection)
return Environment.NewLine + SerializeCollection(collection, nestingLevel);

return SerializeByFieldsAndProperties(obj, nestingLevel);
}

var identation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();
private string SerializeByFieldsAndProperties(object obj, int nestingLevel)
{
var type = obj.GetType();
sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
}
var identation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder(type.Name + Environment.NewLine);

foreach (var member in GetNonExcludedPropertyOrFields(type))
sb.Append(identation + member.Name + " = " + GetSerializedMemberOrDefault(obj, member, nestingLevel + 1));

return sb.ToString();
}

private string GetSerializedMemberOrDefault(object obj, IPropertyOrField member, int nestingLevel)
{
var isMemberSerialized = MemberSerializers.ContainsKey(member.UnderlyingMember);
var memberValue = member.GetValue(obj);

return isMemberSerialized
? MemberSerializers[member.UnderlyingMember](memberValue) + Environment.NewLine
: PrintToString(memberValue, nestingLevel);
}

private IEnumerable<IPropertyOrField> GetNonExcludedPropertyOrFields(Type type)
{
return type
.GetFieldsAndProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => !excludedMembers.Contains(x.UnderlyingMember)
&& !excludedTypes.Contains(x.DataType));
}

private string SerializeCollection(ICollection collection, int nestingLevel)
{
var serialization = new StringBuilder();
var index = 0;

if (collection is IDictionary dictionary)
foreach (var key in dictionary.Keys)
serialization.Append(PrintCollectionItem(key, dictionary[key], nestingLevel));
else foreach (var item in collection)
serialization.Append(PrintCollectionItem(index++, item , nestingLevel));

return serialization.ToString();
}

private string PrintCollectionItem(object key, object value, int nestingLevel)
{
var identation = new string('\t', nestingLevel);
var isFinalOrCollection = finalTypes.Contains(value.GetType()) || value is ICollection;

var serialization = new StringBuilder();
serialization.Append($"{identation}[{PrintToString(key, nestingLevel).TrimEnd()}]:{(isFinalOrCollection ? "" : Environment.NewLine)}");
serialization.Append($"{(isFinalOrCollection ? " " : identation + '\t')}{PrintToString(value, nestingLevel + 1)}");

return serialization.ToString();
}
}
}
13 changes: 13 additions & 0 deletions ObjectPrinting/PropertyOrField/IPropertyOrField.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Reflection;

namespace ObjectPrinting.PropertyOrField
{
internal interface IPropertyOrField
{
string Name { get; }
Type DataType { get; }
MemberInfo UnderlyingMember { get; }
Func<object, object> GetValue { get; }
}
}
Loading