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

Ослина Анастасия #215

Open
wants to merge 4 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,5 @@ _Pvt_Extensions
.fake/

.idea/

ObjectPrinting/Tests/Snapshots/*.received.txt
8 changes: 8 additions & 0 deletions ObjectPrinting/Interfaces/IPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace ObjectPrinting.Interfaces;

public interface IPrintingConfig<TOwner, TType>
{
public PrintingConfig<TOwner> Using(Func<TType, string> print);
}
27 changes: 27 additions & 0 deletions ObjectPrinting/MemberInfoExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Reflection;

namespace ObjectPrinting;

internal static class MemberInfoExtensions
{
public static object? GetValue(this MemberInfo memberInfo, object? obj)
{
return memberInfo.MemberType switch
{
MemberTypes.Field => ((FieldInfo)memberInfo).GetValue(obj),
MemberTypes.Property => ((PropertyInfo)memberInfo).GetValue(obj),
_ => throw new ArgumentException($"Member type {memberInfo.MemberType} is not supported.")
};
}

public static Type GetMemberType(this MemberInfo memberInfo)
{
return memberInfo.MemberType switch
{
MemberTypes.Field => ((FieldInfo)memberInfo).FieldType,
MemberTypes.Property => ((PropertyInfo)memberInfo).PropertyType,
_ => throw new ArgumentException($"Member type {memberInfo.MemberType} is not supported.")
};
}
}
28 changes: 28 additions & 0 deletions ObjectPrinting/MemberPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using ObjectPrinting.Interfaces;

namespace ObjectPrinting;

public class MemberPrintingConfig<TOwner, TPropType> : IPrintingConfig<TOwner, TPropType>
{
private readonly PrintingConfig<TOwner> parent;
private readonly Dictionary<MemberInfo, Func<object, string>> customSerializers;
private readonly MemberInfo memberInfo;

public MemberPrintingConfig(PrintingConfig<TOwner> parent,
Dictionary<MemberInfo, Func<object, string>> customSerializers,
MemberInfo memberInfo)
{
this.parent = parent;
this.customSerializers = customSerializers;
this.memberInfo = memberInfo;
}

public PrintingConfig<TOwner> Using(Func<TPropType, string> print)
{
customSerializers[memberInfo] = obj => print((TPropType)obj);
return parent;
}
}
9 changes: 9 additions & 0 deletions ObjectPrinting/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace ObjectPrinting;

public static class ObjectExtensions
{
public static string PrintToString<T>(this T obj)
{
return ObjectPrinter.For<T>().PrintToString(obj);
}
}
1 change: 1 addition & 0 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="Verify.NUnit" Version="28.4.0" />
</ItemGroup>
</Project>
194 changes: 176 additions & 18 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,199 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using ObjectPrinting.Interfaces;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
malinaboky marked this conversation as resolved.
Show resolved Hide resolved
{
public string PrintToString(TOwner obj)
private readonly HashSet<Type> FinalTypes = [
typeof(string), typeof(DateTime), typeof(TimeSpan), typeof(Guid)
];
private readonly HashSet<Type> excludedTypes = [];
private readonly HashSet<MemberInfo> excludedMembers = [];
private readonly Dictionary<MemberInfo, Func<object, string>> customMemberSerializers = [];
private readonly Dictionary<Type, Func<object, string>> customTypeSerializers = [];
private int maxNestingDepth = 50;

public PrintingConfig<TOwner> SetDepth(int depth)
{
return PrintToString(obj, 0);
maxNestingDepth = depth;
return this;
}

private string PrintToString(object obj, int nestingLevel)
// После долгих размышлений, как расширить FinalTypes, я только придумала дать возможность пользователю
// расширить нужные ему типы, которые посложнее примитивов
public PrintingConfig<TOwner> AddFinalTypes(IEnumerable<Type> types)
{
//TODO apply configurations
FinalTypes.UnionWith(types);
return this;
}

public PrintingConfig<TOwner> AddFinalType<TFinalType>()
{
FinalTypes.Add(typeof(TFinalType));
return this;
}

public IPrintingConfig<TOwner, TPropType> Printing<TPropType>()
{
return new TypePrintingConfig<TOwner, TPropType>(
this,
customTypeSerializers);
}

public IPrintingConfig<TOwner, TPropType> Printing<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
if (memberSelector.Body is not MemberExpression memberInfo)
throw new ArgumentException($"Expression '{memberSelector}' refers to a method, not a property/field.");
return new MemberPrintingConfig<TOwner, TPropType>(
this,
customMemberSerializers,
memberInfo.Member);
}

public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
if (memberSelector.Body is not MemberExpression memberInfo)
throw new ArgumentException($"Expression '{memberSelector}' refers to a method, not a property/field.");
excludedMembers.Add(memberInfo.Member);
return this;
}

internal PrintingConfig<TOwner> Excluding<TPropType>()
{
excludedTypes.Add(typeof(TPropType));
return this;
}

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

private string? PrintToString(object? obj, int nestingLevel, HashSet<object> visitedObjects)
{
if (maxNestingDepth < nestingLevel)
return $"The maximum recursion depth {maxNestingDepth} has been reached";

if (obj == null)
return "null" + Environment.NewLine;
return "null";

var finalTypes = new[]
if (visitedObjects.Contains(obj))
return "Cycling reference!";

var objType = obj.GetType();
if (objType.IsPrimitive || FinalTypes.Contains(objType))
{
if (customTypeSerializers.TryGetValue(objType, out var serializer))
return serializer(obj);

return obj is IFormattable formattable
? formattable.ToString(null, CultureInfo.InvariantCulture)
: obj.ToString();
}

return obj switch
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
IDictionary dictionary => PrintToStringIDictionary(dictionary, nestingLevel, visitedObjects),
IEnumerable enumerable => PrintToStringIEnumerable(enumerable, nestingLevel, visitedObjects),
_ => PrintToStringObject(obj, nestingLevel, visitedObjects)
};
if (finalTypes.Contains(obj.GetType()))
return obj + Environment.NewLine;
}

private bool TryUseCustomSerializer(object value, MemberInfo memberInfo, out string? str)
{
str = null;

if (customMemberSerializers.TryGetValue(memberInfo, out var lambda) ||
customTypeSerializers.TryGetValue(memberInfo.GetMemberType(), out lambda))
{
str = lambda(value);
return true;
}

return false;
}

private IEnumerable<MemberInfo> GetIncludedMembers(Type type)
{
var members = type.GetMembers()
.Where(m => m is PropertyInfo or FieldInfo)
.Where(m => !IsExcluded(m));
return members;
}

private bool IsExcluded(MemberInfo memberInfo)
{
return excludedMembers.Contains(memberInfo) || excludedTypes.Contains(memberInfo.GetMemberType());
}

var identation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();
private string PrintToStringObject(object obj, int nestingLevel, HashSet<object> visitedObjects)
{
var indentation = new string('\t', nestingLevel + 1);
var builder = new StringBuilder();
var type = obj.GetType();
sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
builder.Append(type.Name);
visitedObjects.Add(obj);

foreach (var memberInfo in GetIncludedMembers(type))
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
var value = memberInfo.GetValue(obj);
var serializedStr = TryUseCustomSerializer(value, memberInfo, out var str)
? str
: PrintToString(value, nestingLevel + 1, visitedObjects);
builder.Append('\n')
.Append(indentation)
.Append(memberInfo.Name)
.Append(" = ")
.Append(serializedStr);
}
return sb.ToString();

visitedObjects.Remove(obj);
return builder.ToString();
}

private string PrintToStringIDictionary(IDictionary dictionary, int nestingLevel, HashSet<object> visitedObjects)
{
var indentation = new string('\t', nestingLevel + 1);
var builder = new StringBuilder();
builder.Append(dictionary.GetType().Name);
visitedObjects.Add(dictionary);

foreach (DictionaryEntry item in dictionary)
builder.Append('\n')
.Append(indentation)
.Append(PrintToString(item.Key, nestingLevel + 1, visitedObjects))
.Append(" = ")
.Append(PrintToString(item.Value, nestingLevel + 1, visitedObjects));

visitedObjects.Remove(dictionary);
return builder.ToString();
}

private string PrintToStringIEnumerable(IEnumerable collection, int nestingLevel, HashSet<object> visitedObjects)
{
var indentation = new string('\t', nestingLevel + 1);
var builder = new StringBuilder();
var index = 0;
builder.Append(collection.GetType().Name);
visitedObjects.Add(collection);

foreach (var item in collection)
builder.Append('\n')
.Append(indentation)
.Append(index++)
.Append(" = ")
.Append(PrintToString(item, nestingLevel + 1, visitedObjects));

visitedObjects.Remove(collection);
return builder.ToString();
}
}
}
25 changes: 25 additions & 0 deletions ObjectPrinting/PrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Globalization;
using ObjectPrinting.Interfaces;

namespace ObjectPrinting;

public static class PrintingConfigExtensions
{
public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> config)
{
return config(ObjectPrinter.For<T>()).PrintToString(obj);
}

public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(this IPrintingConfig<TOwner, string> config, int maxLen)
{
return config.Using(x => x == null ? "null" : x[..Math.Min(x.Length, maxLen)]);
}

public static PrintingConfig<TOwner> SetCulture<TOwner, TPropType>(this IPrintingConfig<TOwner, TPropType> config,
CultureInfo culture)
where TPropType: IFormattable
{
return config.Using(x => x.ToString(null, culture));
}
}
27 changes: 0 additions & 27 deletions ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs

This file was deleted.

Loading