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

Минеев Максим #233

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions ObjectPrinting/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ObjectPrinting;

public static class ObjectExtensions
{
public static string PrintToString<T>(this T obj) => ObjectPrinter.For<T>().PrintToString(obj);
}
12 changes: 4 additions & 8 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
namespace ObjectPrinting
namespace ObjectPrinting;

public static class ObjectPrinter
{
public class ObjectPrinter
{
public static PrintingConfig<T> For<T>()
{
return new PrintingConfig<T>();
}
}
public static PrintingConfig<T> For<T>() => new();
}
6 changes: 0 additions & 6 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,4 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>
</Project>
208 changes: 173 additions & 35 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,179 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace ObjectPrinting
namespace ObjectPrinting;

public class PrintingConfig<TOwner>
{
public class PrintingConfig<TOwner>
{
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()))
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.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
}
return sb.ToString();
}
internal readonly Dictionary<Type, Func<object, string>> TypePrinters = new();
internal readonly Dictionary<string, Func<object, string>> PropertyPrinters = new();
internal IFormatProvider Culture = CultureInfo.InvariantCulture;
internal readonly Dictionary<string, int> MaxLengths = new();
private readonly HashSet<Type> excludedTypes = [];
private readonly HashSet<string> excludedProperties = [];
private readonly HashSet<object> visited = [];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Посмотри на логику visited, тоже может быть баг


private readonly HashSet<Type> finalTypes =
[typeof(int), typeof(double), typeof(float), typeof(DateTime), typeof(TimeSpan), typeof(string)];

public ITypePrintingConfig<TOwner, TType> Printing<TType>() =>
new TypePrintingConfig<TOwner, TType>(this);

public IPropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> memberSelector)
{
var propertyName = GetPropertyName(memberSelector);
return new PropertyPrintingConfig<TOwner, TPropType>(this, propertyName);
}

public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
var propertyName = GetPropertyName(memberSelector);
excludedProperties.Add(propertyName);
return this;
}

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

public string PrintToString(TOwner obj) => PrintToString(obj, 0);

private static string GetPropertyName<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
if (memberSelector.Body is not MemberExpression member)
throw new ArgumentException("Expression must be a property access.");

return member.Member.Name;
}

private string PrintToString(object? obj, int nestingLevel)
{
if (obj == null) return "null";
var identation = new string('\t', nestingLevel);

var type = obj.GetType();
if (!type.IsValueType && !type.IsPrimitive && type != typeof(string) && !visited.Add(obj))
return identation + $"Cycle dependency: {obj}";
if (finalTypes.Contains(obj.GetType())) return obj.ToString() ?? string.Empty;

var sb = new StringBuilder();
sb.Append(identation + type.Name);
if (obj is not string && type.IsAssignableTo(typeof(IEnumerable)))
return PrintEnumerable((IEnumerable)obj, nestingLevel);

sb.AppendLine(PrintFieldsToString(obj, nestingLevel));
sb.Append(PrintPropertiesToString(obj, nestingLevel));

return sb.ToString();
}

private string PrintPropertiesToString(object obj, int nestingLevel)
{
var type = obj.GetType();
var identation = new string('\t', nestingLevel + 1);
var items = (from propertyInfo in type.GetProperties()
select Serialize(propertyInfo.GetValue(obj), propertyInfo.PropertyType, propertyInfo.Name, nestingLevel + 1)
into serializedProperty
where serializedProperty != string.Empty
select identation + serializedProperty).ToList();

return string.Join(",\n", items);
}

private string PrintFieldsToString(object obj, int nestingLevel)
{
var type = obj.GetType();
var identation = new string('\t', nestingLevel + 1);
var items = (from fieldInfo in type.GetFields()
select Serialize(fieldInfo.GetValue(obj), fieldInfo.FieldType, fieldInfo.Name, nestingLevel + 1)
into serializedProperty
where serializedProperty != string.Empty
select identation + serializedProperty).ToList();

return string.Join(",\n", items);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Достаточно много дублирования

private string Serialize<T>(T valueToSerialize, Type type, string name, int level)
{
if (excludedTypes.Contains(type) || excludedProperties.Contains(name)) return string.Empty;
if (valueToSerialize is null) return name + " = " + "null";

var value = "";
if (type.IsAssignableTo(typeof(IFormattable)))
value = ((IFormattable)valueToSerialize).ToString(null, Culture);
if (PropertyPrinters.TryGetValue(name, out var propertyPrinter))
value = propertyPrinter(valueToSerialize);
if (TypePrinters.TryGetValue(type, out var typePrinter))
value = typePrinter(valueToSerialize);
if (type.IsAssignableTo(typeof(IEnumerable)))
value = PrintEnumerable((IEnumerable)valueToSerialize, level);
if (type.IsAssignableTo(typeof(KeyValuePair<,>)))
value = PrintKeyValuePair(valueToSerialize);
if (type.IsAssignableTo(typeof(IDictionary)))
value = PrintDictionary((IDictionary)valueToSerialize, level);
if (valueToSerialize is string stringValue && MaxLengths.TryGetValue(name, out var maxLength))
value = stringValue[..Math.Min(value.Length, maxLength)];
if (value == "") value = PrintToString(valueToSerialize, level + 1);
if (value.Length > 0) return name + " = " + value;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

давай переносы сделаем одинаковые

return string.Empty;
}

private string PrintEnumerable(IEnumerable? enumerable, int nestingLevel)
{
var bracketIdentation = new string('\t', nestingLevel);
var itemIdentation = new string('\t', nestingLevel + 1);
if (enumerable is null) return "null";
if (enumerable is string) return enumerable.ToString() ?? string.Empty;
if (enumerable is IDictionary dictionary) return PrintDictionary(dictionary, nestingLevel);
var elementType = GetElementType(enumerable);
if (elementType.IsAssignableTo(typeof(IEnumerable)))
return
$"[\n{string.Join(",\n", enumerable.Cast<object>().Select(item => itemIdentation + PrintEnumerable((IEnumerable)item, nestingLevel + 1)))}\n{bracketIdentation}]";
if (finalTypes.Contains(elementType))
return $"[{string.Join(", ", enumerable.Cast<object>().Select(item => item.ToString() ?? string.Empty))}]";
var result =
$"[\n{string.Join(",\n", enumerable.Cast<object>().Select(item => PrintToString(item, nestingLevel + 1)))}\n{bracketIdentation}]";
return result;
}

private string PrintDictionary(IDictionary? dictionary, int nestingLevel)
{
if (dictionary is null) return "null";
if (dictionary.Count == 0) return "{}";
var bracketIdentation = new string('\t', nestingLevel);
var itemIdentation = new string('\t', nestingLevel + 1);
var serializedItems = (from object key in dictionary.Keys
select $"{PrintToString(key, nestingLevel + 1)}: {PrintToString(dictionary[key], nestingLevel + 1)}")
.ToList();
var result =
$"{{\n{itemIdentation}{string.Join($",\n{itemIdentation}", serializedItems)}\n{bracketIdentation}}}";
return result;
}

private string PrintKeyValuePair(object keyValuePair)
{
if (keyValuePair is KeyValuePair<object, object> keyValue)
return $"{PrintToString(keyValue.Key, 0)} = {PrintToString(keyValue.Value, 0)}";

return string.Empty;
}

private Type GetElementType(IEnumerable? enumerable)
{
if (enumerable is null) return typeof(object);
var enumerableType = enumerable.GetType()
.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));

return enumerableType?.GetGenericArguments().FirstOrDefault() ?? typeof(object);
}
}
25 changes: 25 additions & 0 deletions ObjectPrinting/PropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace ObjectPrinting;

public class PropertyPrintingConfig<TOwner, TPropType>(PrintingConfig<TOwner> printingConfig, string propertyName)
: IPropertyPrintingConfig<TOwner, TPropType>
{
public string PropertyName => propertyName;

public PrintingConfig<TOwner> Using(Func<TPropType, string> print)
{
if (!printingConfig.PropertyPrinters.TryAdd(propertyName, obj => print((TPropType)obj)))
printingConfig.PropertyPrinters[propertyName] = obj => print((TPropType)obj);
return printingConfig;
}

PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig;
}

public interface IPropertyPrintingConfig<TOwner, out TPropType>
{
string PropertyName { get; }
PrintingConfig<TOwner> ParentConfig { get; }
PrintingConfig<TOwner> Using(Func<TPropType, string> print);
}
19 changes: 19 additions & 0 deletions ObjectPrinting/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace ObjectPrinting;

public static class PropertyPrintingConfigExtensions
{
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 IPropertyPrintingConfig<TOwner, string> propConfig,
int maxLen)
{
propConfig.ParentConfig.MaxLengths.TryAdd(propConfig.PropertyName, maxLen);
return propConfig.ParentConfig;
}
}
10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectExtensions.cs

This file was deleted.

10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectPrinter.cs

This file was deleted.

62 changes: 0 additions & 62 deletions ObjectPrinting/Solved/PrintingConfig.cs

This file was deleted.

Loading