New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

TinyString

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tinystring - nuget Package Compare versions

Comparing version
0.1.0
to
0.2.0
lib/netstandard2.1/TinyString.dll

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

+0
-3

@@ -5,9 +5,6 @@ <?xml version="1.0" encoding="utf-8"?>

<Default Extension="psmdcp" ContentType="application/vnd.openxmlformats-package.core-properties+xml" />
<Default Extension="cs" ContentType="application/octet" />
<Default Extension="csproj" ContentType="application/octet" />
<Default Extension="dll" ContentType="application/octet" />
<Default Extension="md" ContentType="application/octet" />
<Default Extension="nuspec" ContentType="application/octet" />
<Default Extension="pdb" ContentType="application/octet" />
<Default Extension="png" ContentType="application/octet" />
</Types>

@@ -66,3 +66,7 @@ # TinyString

| `.Keys(NamingFormat.X)` | Key naming style | `PascalCase` |
| `.NullAs("…")` | Token used for null values | `"null"` |
| `.Only(x => x.A, x => x.B)` | Whitelist — show only these properties | all |
Collection items are rendered based on the active style: joined with `", "` in single-line, or on their own line prefixed with `|_ ` in multi-line. Multi-line items are indented to align under the prefix, so nested structures stay readable at any depth.
Available naming formats: `PascalCase`, `CamelCase`, `SnakeCase`, `KebabCase`, `HumanCase`.

@@ -94,3 +98,13 @@

| `.Decimals(n)` | Decimal places for this property |
| `.MaxItems(n)` | Truncate a collection; appends `"... and N more"` |
| `.When(v => …)` | Show only when the predicate is true |
| `.ValueFormat(v => …)` | Full control over value rendering |
`.Only()` is the shorthand for whitelisting a few properties without chaining multiple `.Ignore()` calls:
```csharp
animal.Stringify(o => o.Only(x => x.Name, x => x.Species));
// → "Animal. Name: Mittens, Species: Cat"
```
---

@@ -100,4 +114,8 @@

Nested objects are stringified automatically using their own defaults:
Nested objects are rendered using the first match in this priority order:
1. A `ForNested<T>()` configuration registered on the parent
2. An overridden `ToString()` on the nested type
3. Reflection over public properties (same defaults as zero-config)
```csharp

@@ -109,2 +127,44 @@ var order = new Order { Product = new Product { Name = "Book", Price = 12.99 }, Qty = 2 };

### ForNested
Configure a nested type directly from the root call. The configuration applies wherever that type appears — as a property or inside a collection:
```csharp
zoo.Stringify(o => o
.MultiLine()
.ForNested<Animal>(a => a
.NoLabel()
.Separator(" ")
.For(x => x.Name).NoKey()
.For(x => x.Species).Prefix("(").Suffix(")").NoKey()
.For(x => x.Weight).NoKey().Suffix("kg"))
.For(x => x.EntrancePrice).Prefix("$").Decimals(0));
```
```
Zoo
EntrancePrice: $15
Animals:
|_ Mittens (Cat) 4.50kg
|_ Tony (Tiger) 120.30kg
```
### ToString()
If a nested type overrides `ToString()`, that is used automatically. If `ToString()` calls `Stringify()`, it recurses:
```csharp
public class Animal
{
public string Name { get; set; }
public string Species { get; set; }
public override string ToString() => this.Stringify(o => o
.NoLabel()
.For(x => x.Name).NoKey()
.For(x => x.Species).Prefix("(").Suffix(")").NoKey());
// → "Mittens (Cat)"
}
```
---

@@ -111,0 +171,0 @@

@@ -5,3 +5,3 @@ <?xml version="1.0" encoding="utf-8"?>

<id>TinyString</id>
<version>0.1.0</version>
<version>0.2.0</version>
<authors>Gianluca Belvisi</authors>

@@ -15,8 +15,7 @@ <license type="expression">MIT</license>

<tags>string extensions stringify smart tostring pretty-print</tags>
<repository type="git" url="https://github.com/gianlucabelvisi/TinyTools" commit="9eec7761022e45ac1993f8442db2ae257324f7d0" />
<repository type="git" url="https://github.com/gianlucabelvisi/TinyTools" commit="7ccb493817048510cbf7cfb50b185de4e99fee01" />
<dependencies>
<group targetFramework="net8.0" />
<group targetFramework="net9.0" />
<group targetFramework=".NETStandard2.1" />
</dependencies>
</metadata>
</package>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

namespace TinyString;
public enum NamingFormat
{
CamelCase,
PascalCase,
SnakeCase,
KebabCase,
HumanCase
}
// <auto-generated/>
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Threading;
global using System.Threading.Tasks;
// <auto-generated/>
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Threading;
global using System.Threading.Tasks;
namespace TinyString;
public enum PrintStyle
{
SingleLine,
MultiLine
}
using System.Linq.Expressions;
namespace TinyString;
/// <summary>
/// Fluent builder for configuring how a single property is rendered.
/// Obtained via <see cref="StringifyOptions{T}.For{TProp}"/>.
/// </summary>
public sealed class PropertyBuilder<T, TProp>
{
private readonly StringifyOptions<T> _parent;
private readonly PropertyConfig _config;
internal PropertyBuilder(StringifyOptions<T> parent, PropertyConfig config)
{
_parent = parent;
_config = config;
}
/// <summary>Exclude this property from the output.</summary>
public PropertyBuilder<T, TProp> Ignore() { _config.Ignored = true; return this; }
/// <summary>Override the display name of this property's key.</summary>
public PropertyBuilder<T, TProp> As(string name) { _config.Label = name; return this; }
/// <summary>Render only the value — no key prefix.</summary>
public PropertyBuilder<T, TProp> NoKey() { _config.ShowKey = false; return this; }
/// <summary>Prepend text immediately before this property's value.</summary>
public PropertyBuilder<T, TProp> Prefix(string prefix) { _config.Prefix = prefix; return this; }
/// <summary>Append text immediately after this property's value.</summary>
public PropertyBuilder<T, TProp> Suffix(string suffix) { _config.Suffix = suffix; return this; }
/// <summary>
/// Override the separator used between items when this property is a collection.
/// If the separator starts with <c>\n</c> it is also prepended before the first item.
/// </summary>
public PropertyBuilder<T, TProp> Separator(string separator) { _config.CollectionSeparator = separator; return this; }
/// <summary>Override the number of decimal places for this property's value.</summary>
public PropertyBuilder<T, TProp> Decimals(int decimals) { _config.Decimals = decimals; return this; }
/// <summary>
/// Continue configuring the next property. Equivalent to calling
/// <c>.For()</c> on the parent <see cref="StringifyOptions{T}"/>.
/// </summary>
public PropertyBuilder<T, TNext> For<TNext>(Expression<Func<T, TNext>> selector)
=> _parent.For(selector);
}
using System.Text.RegularExpressions;
namespace TinyString;
using System.Text;
public static class StringExtensions
{
/// <summary>
/// Naive CamelCase conversion: first letter lowercase, rest unchanged.
/// (You can enhance with better rules for acronyms, underscores, etc.)
/// </summary>
public static string ToCamelCase(this string str)
{
if (string.IsNullOrEmpty(str)) return str;
if (str.Length == 1) return str.ToLower();
return char.ToLower(str[0]) + str.Substring(1);
}
/// <summary>
/// Simple SnakeCase conversion: insert underscores before uppercase letters, then lower everything.
/// (Again, can be improved for various corner cases.)
/// </summary>
public static string ToSnakeCase(this string str)
{
if (string.IsNullOrEmpty(str)) return str;
var sb = new StringBuilder();
foreach (char c in str)
{
if (char.IsUpper(c) && sb.Length > 0)
{
sb.Append('_');
}
sb.Append(char.ToLower(c));
}
return sb.ToString();
}
/// <summary>
/// Converts a string to kebab-case.
/// </summary>
public static string ToKebabCase(this string str)
{
if (string.IsNullOrEmpty(str)) return str;
var snakeCase = str.ToSnakeCase();
return snakeCase.Replace('_', '-');
}
/// <summary>
/// Converts a string to Human Case with spaces between words.
/// </summary>
public static string ToHumanCase(this string str)
{
if (string.IsNullOrEmpty(str)) return str;
var sb = new StringBuilder();
for (int i = 0; i < str.Length; i++)
{
char c = str[i];
// Add space before uppercase letters if not the first character
// and the previous character is not already a space
if (i > 0 && char.IsUpper(c) && !char.IsWhiteSpace(str[i - 1]))
{
sb.Append(' ');
}
sb.Append(c);
}
return sb.ToString();
}
/// <summary>
/// Simple Slug conversion: remove non-alphanumeric characters and lowercase everything.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string ToSlug(this string str) => Regex.Replace(str, "[^a-zA-Z0-9]", "").ToLower();
/// <summary>
/// Remove newline characters from a string.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string OneLine(this string? str) => str?.Replace("\n", " ").Replace("\r", " ") ?? "";
/// <summary>
/// Split in the capped part and the residual part.
/// </summary>
/// <param name="str"></param>
/// <param name="length"></param>
/// <returns></returns>
public static (string capped, string residual) CapLength(this string str, int length)
=> new
(
str.Length <= length ? str : str[..length],
str.Length > length ? str[length..] : string.Empty
);
/// <summary>
/// Check if a string is composed of only digits.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static bool IsDigitsOnly(this string str) => str.All(char.IsDigit);
/// <summary>
/// Remove all non-digit characters from a string.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string KeepDigits(this string str)
=> string.IsNullOrEmpty(str) ? string.Empty : new string(str.Where(char.IsDigit).ToArray());
/// <summary>
/// Check if a string is null or empty.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static bool IsNullOrEmpty(this string? str) => string.IsNullOrEmpty(str);
/// <summary>
/// Check if a string is not null or empty.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static bool IsNotEmpty(this string? str) => !string.IsNullOrEmpty(str);
/// <summary>
/// Join a sequence
/// </summary>
/// <param name="source"></param>
/// <param name="separator"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static string Join<T>(this IEnumerable<T> source, string separator) => string.Join(separator, source);
}
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Text;
namespace TinyString;
public static class Stringifier
{
// ── New fluent API ──────────────────────────────────────────────────────
/// <summary>
/// Converts an object to a human-readable string.
/// Pass an optional <paramref name="configure"/> action to customise the output
/// with the fluent builder; omit it for sensible defaults.
/// </summary>
public static string? Stringify<T>(this T obj, Action<StringifyOptions<T>>? configure = null)
{
if (obj is null) return null;
// When called with no options, honour any legacy attributes so that
// existing code keeps working without changes.
if (configure is null && HasLegacyAttributes(typeof(T)))
return StringifyLegacy(obj);
var options = new StringifyOptions<T>();
configure?.Invoke(options);
return StringifyWithOptions(obj, options);
}
private static string StringifyWithOptions<T>(T obj, StringifyOptions<T> opts)
{
var type = typeof(T);
var sb = new StringBuilder();
// Header
if (opts._showHeader)
{
sb.Append(opts._header ?? type.Name);
if (opts._style == PrintStyle.MultiLine)
sb.AppendLine();
else
sb.Append(". ");
}
var props = type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetIndexParameters().Length == 0 && p.CanRead);
var first = true;
foreach (var prop in props)
{
opts._properties.TryGetValue(prop.Name, out var cfg);
if (cfg?.Ignored == true) continue;
var keyName = ConvertName(cfg?.Label ?? prop.Name, opts._namingFormat);
var showKey = cfg?.ShowKey ?? true;
var prefix = cfg?.Prefix ?? "";
var suffix = cfg?.Suffix ?? "";
var decimals = cfg?.Decimals ?? opts._decimals;
var collSep = cfg?.CollectionSeparator
?? opts._collectionSeparator
?? (opts._style == PrintStyle.MultiLine ? "\n|_ " : ", ");
var value = prop.GetValue(obj);
var rendered = prefix + ConvertValue(value, decimals, collSep) + suffix;
var line = showKey ? $"{keyName}: {rendered}" : rendered;
if (opts._style == PrintStyle.SingleLine)
{
if (!first) sb.Append(opts._separator);
sb.Append(line);
first = false;
}
else
{
sb.AppendLine(line);
}
}
return sb.ToString().TrimEnd();
}
// ── Legacy attribute-based API (deprecated) ─────────────────────────────
/// <summary>
/// Converts an object to a string using the legacy attribute-based configuration
/// (<c>[Stringify]</c>, <c>[StringifyProperty]</c>, <c>[StringifyIgnore]</c>).
/// </summary>
/// <remarks>
/// This overload is kept for backwards compatibility. Migrate to
/// <see cref="Stringify{T}(T, Action{StringifyOptions{T}})"/> with the fluent API.
/// Attribute-based configuration will be removed in a future major version.
/// </remarks>
[Obsolete(
"Attribute-based configuration is deprecated. " +
"Use Stringify<T>(Action<StringifyOptions<T>>) with the fluent API instead. " +
"Attribute-based configuration will be removed in a future major version.")]
public static string? Stringify(this object? obj) =>
obj is null ? null : StringifyLegacy(obj);
// Called internally (no obsolete warning) for legacy-attribute objects and nested objects.
internal static string StringifyLegacy(object obj)
{
var type = obj.GetType();
var classAttr = type.GetCustomAttribute<StringifyAttribute>() ?? new StringifyAttribute();
var sb = new StringBuilder();
if (classAttr.Emoji.IsNotEmpty())
sb.Append(classAttr.Emoji);
if (classAttr.PrintClassName)
{
if (sb.Length > 0) sb.Append(' ');
sb.Append(type.Name);
}
if (sb.Length > 0)
{
if (classAttr.PrintStyle == PrintStyle.MultiLine)
sb.AppendLine();
else
sb.Append(classAttr.ClassNameSeparator);
}
var props = type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetIndexParameters().Length == 0 && p.CanRead)
.Where(p => p.GetCustomAttribute<StringifyIgnoreAttribute>() == null);
var first = true;
foreach (var prop in props)
{
var propAttr = prop.GetCustomAttribute<StringifyPropertyAttribute>();
var propName = ConvertName(prop.Name, classAttr.NamingFormat);
var format = propAttr?.Format ?? classAttr.PropertyFormat;
var collSep = propAttr?.CollectionSeparator ?? classAttr.CollectionSeparator;
var decimals = propAttr?.Decimals ?? classAttr.Decimals;
var value = prop.GetValue(obj);
var converted = ConvertValue(value, decimals, collSep);
var line = format.Replace("{k}", propName).Replace("{v}", converted);
if (classAttr.PrintStyle == PrintStyle.SingleLine)
{
if (!first) sb.Append(classAttr.PropertySeparator);
sb.Append(line);
first = false;
}
else
{
sb.AppendLine(line);
}
}
return sb.ToString().TrimEnd();
}
// ── Shared helpers ──────────────────────────────────────────────────────
private static bool HasLegacyAttributes(Type type)
{
if (type.GetCustomAttribute<StringifyAttribute>() != null) return true;
return type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Any(p => p.GetCustomAttribute<StringifyPropertyAttribute>() != null
|| p.GetCustomAttribute<StringifyIgnoreAttribute>() != null);
}
private static string ConvertName(string name, NamingFormat format) => format switch
{
NamingFormat.CamelCase => name.ToCamelCase(),
NamingFormat.SnakeCase => name.ToSnakeCase(),
NamingFormat.KebabCase => name.ToKebabCase(),
NamingFormat.HumanCase => name.ToHumanCase(),
_ => name,
};
internal static string ConvertValue(object? value, int decimals, string collectionSeparator) =>
value switch
{
null => "null",
string s => s,
bool b => b.ToString(),
Enum e => e.ToString(),
int or long or short
or byte or uint
or ulong or ushort
or sbyte => value.ToString()!,
IEnumerable enumerable => ConvertCollection(enumerable, decimals, collectionSeparator),
float f => f.ToString($"F{decimals}", CultureInfo.InvariantCulture),
double d => d.ToString($"F{decimals}", CultureInfo.InvariantCulture),
decimal m => m.ToString($"F{decimals}", CultureInfo.InvariantCulture),
_ => TrySmartEnum(value) ?? StringifyLegacy(value),
};
private static string ConvertCollection(IEnumerable enumerable, int decimals, string separator)
{
var items = enumerable.Cast<object?>().Select(i => ConvertValue(i, decimals, separator));
var joined = string.Join(separator, items);
// If the separator starts with a newline the caller wants it also before the first item.
return separator.Contains('\n') ? separator + joined : joined;
}
private static string? TrySmartEnum(object value)
{
var type = value.GetType();
var nameProp = type.GetProperty("Name");
if (nameProp?.PropertyType != typeof(string)) return null;
bool IsSmartEnum(Type t) =>
t.GetInterfaces().Any(i => i.Name.Contains("SmartEnum"));
var cursor = type;
while (cursor != null && cursor != typeof(object))
{
if (cursor.Name.Contains("SmartEnum") || IsSmartEnum(cursor))
return nameProp.GetValue(value) as string;
cursor = cursor.BaseType;
}
return IsSmartEnum(type) ? nameProp.GetValue(value) as string : null;
}
}
namespace TinyString;
/// <summary>
/// An attribute to control how classes are stringified via the .Stringify() extension method.
/// </summary>
/// <remarks>
/// Deprecated: use the fluent <c>Stringify(o =&gt; o.MultiLine()...)</c> API instead.
/// This attribute will be removed in a future major version.
/// </remarks>
[Obsolete(
"Attribute-based configuration is deprecated. " +
"Use the fluent Stringify<T>(Action<StringifyOptions<T>>) API instead. " +
"This attribute will be removed in a future major version.")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class StringifyAttribute : Attribute
{
/// <summary>
/// Determines whether properties are printed on one line
/// or multiple lines. Default = <see cref="PrintStyle.SingleLine"/>.
/// </summary>
public PrintStyle PrintStyle { get; set; } = PrintStyle.SingleLine;
/// <summary>
/// If <c>true</c>, prints the class name (or an emoji, if set) at the start
/// before printing any properties. Default = <c>true</c>.
/// </summary>
public bool PrintClassName { get; set; } = true;
/// <summary>
/// If set, prints this emoji at the start instead of the class name.
/// If <see cref="PrintClassName"/> is also <c>true</c> but <c>Emoji</c> is specified,
/// the emoji takes precedence. Default = <c>""</c>.
/// </summary>
public string Emoji { get; set; } = "";
/// <summary>
/// A separator inserted immediately after the class name or emoji when
/// <see cref="PrintStyle"/> is SingleLine.
/// Default = <c>". "</c>.
/// </summary>
public string ClassNameSeparator { get; set; } = ". ";
/// <summary>
/// Used to separate properties when <see cref="PrintStyle"/> is SingleLine.
/// Default = <c>", "</c>.
/// </summary>
public string PropertySeparator { get; set; } = ", ";
/// <summary>
/// Used to separate collection items (e.g. elements in a List).
/// Default = <c>"; "</c>.
/// </summary>
public string CollectionSeparator { get; set; } = "; ";
/// <summary>
/// Number of decimal places to use when printing floating-point properties.
/// Default = <c>5</c>.
/// </summary>
public int Decimals { get; set; } = 5;
/// <summary>
/// Specifies how property names are converted: PascalCase, CamelCase, SnakeCase, etc.
/// Default = <see cref="NamingFormat.PascalCase"/>.
/// </summary>
public NamingFormat NamingFormat { get; set; } = NamingFormat.PascalCase;
/// <summary>
/// A format string controlling how each property is displayed.
/// <c>{k}</c> is replaced by the property name; <c>{v}</c> is replaced by its value.
/// Default = <c>"{k}: {v}"</c>.
/// </summary>
public string PropertyFormat { get; set; } = "{k}: {v}";
}
namespace TinyString;
/// <remarks>
/// Deprecated: use <c>.For(x =&gt; x.Prop).Ignore()</c> in the fluent API instead.
/// This attribute will be removed in a future major version.
/// </remarks>
[Obsolete(
"Attribute-based configuration is deprecated. " +
"Use .For(x => x.Prop).Ignore() in the fluent Stringify<T>(Action<StringifyOptions<T>>) API instead. " +
"This attribute will be removed in a future major version.")]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class StringifyIgnoreAttribute : Attribute
{
}
using System.Linq.Expressions;
namespace TinyString;
/// <summary>
/// Fluent builder for configuring how an object is stringified.
/// Obtain one via <see cref="Stringifier.Stringify{T}(T, Action{StringifyOptions{T}})"/>.
/// </summary>
public sealed class StringifyOptions<T>
{
// ── Global settings (internal so Stringifier can read them directly) ────
internal PrintStyle _style = PrintStyle.SingleLine;
internal string? _header;
internal bool _showHeader = true;
internal string _separator = ", ";
internal string? _collectionSeparator; // null = auto (", " single-line / "\n|_ " multi-line)
internal int _decimals = 2;
internal NamingFormat _namingFormat = NamingFormat.PascalCase;
// ── Per-property settings ──────────────────────────────────────────────
internal readonly Dictionary<string, PropertyConfig> _properties = new();
// ── Global option methods ──────────────────────────────────────────────
/// <summary>Print each property on its own line.</summary>
public StringifyOptions<T> MultiLine() { _style = PrintStyle.MultiLine; return this; }
/// <summary>Print all properties on a single line (default).</summary>
public StringifyOptions<T> SingleLine() { _style = PrintStyle.SingleLine; return this; }
/// <summary>Override the header shown before the properties (replaces the class name).</summary>
public StringifyOptions<T> Label(string label) { _header = label; return this; }
/// <summary>Hide the header entirely.</summary>
public StringifyOptions<T> NoLabel() { _showHeader = false; return this; }
/// <summary>Separator between properties in single-line mode. Default: <c>", "</c>.</summary>
public StringifyOptions<T> Separator(string separator) { _separator = separator; return this; }
/// <summary>
/// Override the default separator between collection items.
/// By default: <c>", "</c> in single-line mode, <c>"\n|_ "</c> in multi-line mode.
/// </summary>
public StringifyOptions<T> CollectionSeparator(string separator) { _collectionSeparator = separator; return this; }
/// <summary>Default number of decimal places for floating-point values. Default: <c>2</c>.</summary>
public StringifyOptions<T> Decimals(int decimals) { _decimals = decimals; return this; }
/// <summary>Naming format applied to all property keys. Default: <see cref="NamingFormat.PascalCase"/>.</summary>
public StringifyOptions<T> Keys(NamingFormat format) { _namingFormat = format; return this; }
// ── Property configuration ─────────────────────────────────────────────
/// <summary>
/// Begin configuring a specific property. Chain further calls on the returned
/// <see cref="PropertyBuilder{T, TProp}"/> and continue with the next
/// <c>.For()</c> when done.
/// </summary>
public PropertyBuilder<T, TProp> For<TProp>(Expression<Func<T, TProp>> selector)
{
var name = ExtractName(selector);
if (!_properties.TryGetValue(name, out var config))
{
config = new PropertyConfig();
_properties[name] = config;
}
return new PropertyBuilder<T, TProp>(this, config);
}
internal static string ExtractName<TProp>(Expression<Func<T, TProp>> selector) =>
selector.Body is MemberExpression m
? m.Member.Name
: throw new ArgumentException(
"Selector must be a direct property access, e.g. x => x.Name.", nameof(selector));
}
/// <summary>Per-property configuration, populated by <see cref="PropertyBuilder{T, TProp}"/>.</summary>
internal sealed class PropertyConfig
{
public bool Ignored { get; set; }
public string? Label { get; set; }
public bool ShowKey { get; set; } = true;
public string? Prefix { get; set; }
public string? Suffix { get; set; }
public string? CollectionSeparator { get; set; }
public int? Decimals { get; set; }
}
namespace TinyString;
/// <remarks>
/// Deprecated: use the fluent <c>.For(x =&gt; x.Prop).Prefix(...).Suffix(...)</c> API instead.
/// This attribute will be removed in a future major version.
/// </remarks>
[Obsolete(
"Attribute-based configuration is deprecated. " +
"Use the fluent Stringify<T>(Action<StringifyOptions<T>>) API instead. " +
"This attribute will be removed in a future major version.")]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class StringifyPropertyAttribute : Attribute
{
/// <summary>
/// Creates a new StringifyPropertyAttribute with optional format.
/// </summary>
/// <param name="format">Optional format string for this property.</param>
public StringifyPropertyAttribute(string? format = null)
{
Format = format;
}
/// <summary>
/// Format string for this property.
/// Use `{k}` for the property name and `{v}` for the value.
/// Default = <c>null</c> (uses class-level format).
/// </summary>
public string? Format { get; set; }
/// <summary>
/// Used to separate collection items (e.g. elements in a List).
/// Default = <c>null</c> (uses class-level separator).
/// </summary>
public string? CollectionSeparator { get; set; }
/// <summary>
/// Number of decimal places to use when printing floating-point properties.
/// Default = <c>null</c> (uses class-level decimal places).
/// </summary>
public int? Decimals { get; set; }
/// <summary>
/// A separator inserted immediately after the class name or emoji.
/// Default = <c>null</c> (uses class-level separator).
/// </summary>
public string? ClassNameSeparator { get; set; }
}
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="Version.props" Condition="Exists('Version.props')" />
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NuGetAudit>false</NuGetAudit>
<!-- NuGet package metadata -->
<PackageId>TinyString</PackageId>
<Version>$(ProjectVersion)</Version>
<Authors>Gianluca Belvisi</Authors>
<Company>gianlucabelvisi.com</Company>
<Description>Lightweight pretty-printer utilities for .NET</Description>
<PackageTags>string extensions stringify smart tostring pretty-print</PackageTags>
<PackageIcon>logo_icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/gianlucabelvisi/TinyTools</PackageProjectUrl>
<RepositoryUrl>https://github.com/gianlucabelvisi/TinyTools</RepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<IncludeSource>true</IncludeSource>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<RootNamespace>TinyString</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
<None Include="logo_icon.png" Pack="true" PackagePath="" />
</ItemGroup>
</Project>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet