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

Fixes #263 Add huminization of collections #268

Closed
Closed
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
1 change: 1 addition & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
###In Development
- [#257](https://github.com/Mehdik/Humanizer/pull/257): Added German localisation for ToOrdinalWords and Ordinalize
- [#261](https://github.com/Mehdik/Humanizer/pull/261): Added future dates to Portuguese - Brazil
- [#268](https://github.com/Mehdik/Humanizer/pull/268): Added humanization of collections

[Commits](https://github.com/MehdiK/Humanizer/compare/v1.25.4...master)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,17 @@ public class CasingExtensions
public string ApplyCase(string input, Humanizer.LetterCasing casing) { }
}

public class CollectionHumanizeExtensions
{
public string Humanize(System.Collections.Generic.IEnumerable<> collection) { }
public string Humanize(System.Collections.Generic.IEnumerable<> collection, System.Func<, > displayFormatter) { }
public string Humanize(System.Collections.Generic.IEnumerable<> collection, string separator) { }
public string Humanize(System.Collections.Generic.IEnumerable<> collection, System.Func<, > displayFormatter, string separator) { }
}

public class Configurator
{
public Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.CollectionFormatters.ICollectionFormatter> CollectionFormatters { get; }
Copy link
Member

Choose a reason for hiding this comment

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

Don't have VS atm; but why did we need to create a CollectionFormatters? Could it not live in Formatters folder?

public Humanizer.DateTimeHumanizeStrategy.IDateTimeHumanizeStrategy DateTimeHumanizeStrategy { get; set; }
public Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.Formatters.IFormatter> Formatters { get; }
public Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.NumberToWords.INumberToWordsConverter> NumberToWordsConverters { get; }
Expand Down Expand Up @@ -194,6 +203,14 @@ public enum LetterCasing
value__,
}

public interface ICollectionFormatter
Copy link
Member

Choose a reason for hiding this comment

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

I like the API

{
string Humanize(System.Collections.Generic.IEnumerable<> collection);
string Humanize(System.Collections.Generic.IEnumerable<> collection, System.Func<, > objectFormatter);
string Humanize(System.Collections.Generic.IEnumerable<> collection, string separator);
string Humanize(System.Collections.Generic.IEnumerable<> collection, System.Func<, > objectFormatter, string separator);
}

public class DefaultFormatter
{
public DefaultFormatter() { }
Expand Down
108 changes: 108 additions & 0 deletions src/Humanizer.Tests/CollectionHumanizeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Collections.Generic;
using Xunit;

namespace Humanizer.Tests
{
public class SomeClass
{
public string SomeString;
public int SomeInt;
public override string ToString()
{
return "ToString";
}
}

public class CollectionHumanizeTests : AmbientCulture
{
public CollectionHumanizeTests() : base("en") { }

private readonly List<SomeClass> _testCollection = new List<SomeClass>
{
new SomeClass
{
SomeInt = 1,
SomeString = "One"
},
new SomeClass
{
SomeInt = 2,
SomeString = "Two"
},
new SomeClass
{
SomeInt = 3,
SomeString = "Three"
}
};

[Fact]
public void HumanizeReturnsOnlyNameWhenCollectionContainsOneItem()
{
var collection = new List<string> { "A String" };

Assert.Equal("A String", collection.Humanize());
}

[Fact]
public void HumanizeUsesSeparatorWhenMoreThanOneItemIsInCollection()
{
var collection = new List<string>
{
"A String",
"Another String",
};

Assert.Equal("A String or Another String", collection.Humanize("or"));
}

[Fact]
public void HumanizeDefaultsSeparatorToAnd()
{
var collection = new List<string>
{
"A String",
"Another String",
};

Assert.Equal("A String and Another String", collection.Humanize());
}

[Fact]
public void HumanizeUsesOxfordComma()
{
var collection = new List<string>
{
"A String",
"Another String",
"A Third String",
};

Assert.Equal("A String, Another String, or A Third String", collection.Humanize("or"));
}

[Fact]
public void HumanizeDefaultsToToString()
{

Assert.Equal("ToString, ToString, or ToString", _testCollection.Humanize("or"));
}

[Fact]
public void HumanizeUsesObjectFormatter()
{

Assert.Equal("SomeObject #1 - One, SomeObject #2 - Two, and SomeObject #3 - Three",
_testCollection.Humanize(sc => string.Format("SomeObject #{0} - {1}", sc.SomeInt, sc.SomeString)));
}

[Fact]
public void HumanizeUsesObjectFormatterWhenSeparatorIsProvided()
{

Assert.Equal("SomeObject #1 - One, SomeObject #2 - Two, or SomeObject #3 - Three",
_testCollection.Humanize(sc => string.Format("SomeObject #{0} - {1}", sc.SomeInt, sc.SomeString),
"or"));
}
}
}
2 changes: 2 additions & 0 deletions src/Humanizer.Tests/Humanizer.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<Compile Include="Bytes\ToStringTests.cs" />
<Compile Include="CasingTests.cs" />
<Compile Include="DateHumanize.cs" />
<Compile Include="CollectionHumanizeTests.cs" />
<Compile Include="Localisation\bg\DateHumanizeTests.cs" />
<Compile Include="Localisation\bg\TimeSpanHumanizeTests.cs" />
<Compile Include="Localisation\cs\DateHumanizeTests.cs" />
Expand Down Expand Up @@ -171,6 +172,7 @@
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
60 changes: 60 additions & 0 deletions src/Humanizer/CollectionHumanizeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using Humanizer.Configuration;


namespace Humanizer
{
/// <summary>
/// Humanizes an IEnumerable into a human readable list
/// </summary>
public static class CollectionHumanizeExtensions
{
/// <summary>
/// Formats the collection for display, calling ToString() on each object and
/// using the default separator for the current culture.
/// </summary>
/// <returns></returns>
public static string Humanize<T>(this IEnumerable<T> collection)
{
return Configurator.CollectionFormatter.Humanize(collection);
}

/// <summary>
/// Formats the collection for display, calling `objectFormatter` on each object
/// and using the default separator for the current culture.
/// </summary>
/// <returns></returns>
public static string Humanize<T>(this IEnumerable<T> collection, Func<T, String> displayFormatter)
Copy link
Member

Choose a reason for hiding this comment

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

No tests for this :(

{
if (displayFormatter == null)
throw new ArgumentNullException("displayFormatter");

return Configurator.CollectionFormatter.Humanize(collection, displayFormatter);
}

/// <summary>
/// Formats the collection for display, calling ToString() on each object
/// and using the provided separator.
/// </summary>
/// <returns></returns>
public static string Humanize<T>(this IEnumerable<T> collection, String separator)
{

return Configurator.CollectionFormatter.Humanize(collection, separator);
}

/// <summary>
/// Formats the collection for display, calling `objectFormatter` on each object
/// and using the provided separator.
/// </summary>
/// <returns></returns>
public static string Humanize<T>(this IEnumerable<T> collection, Func<T, String> displayFormatter, String separator)
Copy link
Member

Choose a reason for hiding this comment

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

or this!?

{
if (displayFormatter == null)
throw new ArgumentNullException("displayFormatter");

return Configurator.CollectionFormatter.Humanize(collection, displayFormatter, separator);
}
}
}
13 changes: 13 additions & 0 deletions src/Humanizer/Configuration/CollectionFormatterRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Humanizer.Localisation.CollectionFormatters;

namespace Humanizer.Configuration
{
internal class CollectionFormatterRegistry : LocaliserRegistry<ICollectionFormatter>
{
public CollectionFormatterRegistry()
: base(new DefaultCollectionFormatter())
{
Register<EnglishCollectionFormatter>("en");
}
}
}
19 changes: 19 additions & 0 deletions src/Humanizer/Configuration/Configurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Humanizer.Localisation.Formatters;
using Humanizer.Localisation.NumberToWords;
using Humanizer.Localisation.Ordinalizers;
using Humanizer.Localisation.CollectionFormatters;

namespace Humanizer.Configuration
{
Expand All @@ -10,6 +11,16 @@ namespace Humanizer.Configuration
/// </summary>
public static class Configurator
{
private static readonly LocaliserRegistry<ICollectionFormatter> _collectionFormatters = new CollectionFormatterRegistry();
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for considering the localisation needs and cleanly implementing it.


/// <summary>
/// A registry of formatters used to format collections based on the current locale
/// </summary>
public static LocaliserRegistry<ICollectionFormatter> CollectionFormatters
{
get { return _collectionFormatters; }
}

private static readonly LocaliserRegistry<IFormatter> _formatters = new FormatterRegistry();
/// <summary>
/// A registry of formatters used to format strings based on the current locale
Expand All @@ -36,6 +47,14 @@ public static LocaliserRegistry<IOrdinalizer> Ordinalizers
{
get { return _ordinalizers; }
}

internal static ICollectionFormatter CollectionFormatter
{
get
{
return CollectionFormatters.ResolveForUiCulture();
}
}

/// <summary>
/// The formatter to be used
Expand Down
5 changes: 5 additions & 0 deletions src/Humanizer/Humanizer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@
<AssemblyOriginatorKeyFile>Humanizer.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="CollectionHumanizeExtensions.cs" />
<Compile Include="Configuration\CollectionFormatterRegistry.cs" />
<Compile Include="Localisation\CollectionFormatters\DefaultCollectionFormatter.cs" />
Copy link
Member

Choose a reason for hiding this comment

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

Nah, it's cool for it to get its own folder.

<Compile Include="Localisation\CollectionFormatters\EnglishCollectionFormatter.cs" />
<Compile Include="Localisation\CollectionFormatters\ICollectionFormatter.cs" />
<Compile Include="Localisation\Formatters\SerbianFormatter.cs" />
<Compile Include="Localisation\Formatters\SlovenianFormatter.cs" />
<Compile Include="Configuration\LocaliserRegistry.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;

namespace Humanizer.Localisation.CollectionFormatters
{
class DefaultCollectionFormatter : ICollectionFormatter
{
protected String DefaultSeparator = "";

public virtual string Humanize<T>(IEnumerable<T> collection)
{
return Humanize(collection, o => o.ToString(), DefaultSeparator);
}

public virtual string Humanize<T>(IEnumerable<T> collection, Func<T, String> objectFormatter)
{
return Humanize(collection, objectFormatter, DefaultSeparator);
}

public virtual string Humanize<T>(IEnumerable<T> collection, String separator)
{
return Humanize(collection, o => o.ToString(), separator);
}

public virtual string Humanize<T>(IEnumerable<T> collection, Func<T, String> objectFormatter, String separator)
{
throw new NotImplementedException("A collection formatter for the current culture has not been implemented yet.");
Copy link
Member

Choose a reason for hiding this comment

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

Haha, cool :)

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Humanizer.Localisation.CollectionFormatters
{
internal class EnglishCollectionFormatter : DefaultCollectionFormatter
{
public EnglishCollectionFormatter()
{
DefaultSeparator = "and";
}

public override string Humanize<T>(IEnumerable<T> collection, Func<T, String> objectFormatter, String separator)
{
if (collection == null)
throw new ArgumentException("collection");

var enumerable = collection as T[] ?? collection.ToArray();

int count = enumerable.Count();

if (count == 0)
return "";

if (count == 1)
return objectFormatter(enumerable.First());

string formatString = count > 2 ? "{0}, {1} {2}" : "{0} {1} {2}";

return String.Format(formatString,
String.Join(", ", enumerable.Take(count - 1).Select(objectFormatter)),
separator,
objectFormatter(enumerable.Skip(count - 1).First()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;

namespace Humanizer.Localisation.CollectionFormatters
{
/// <summary>
/// An interface you should implement to localize Humanize for collections
/// </summary>
public interface ICollectionFormatter
{
/// <summary>
/// Formats the collection for display, calling ToString() on each object.
/// </summary>
/// <returns></returns>
String Humanize<T>(IEnumerable<T> collection);

/// <summary>
/// Formats the collection for display, calling `objectFormatter` on each object.
/// </summary>
/// <returns></returns>
String Humanize<T>(IEnumerable<T> collection, Func<T, String> objectFormatter);

/// <summary>
/// Formats the collection for display, calling ToString() on each object
/// and using `separator` before the final item.
/// </summary>
/// <returns></returns>
String Humanize<T>(IEnumerable<T> collection, String separator);

/// <summary>
/// Formats the collection for display, calling `objectFormatter` on each object
/// and using `separator` before the final item.
/// </summary>
/// <returns></returns>
String Humanize<T>(IEnumerable<T> collection, Func<T, String> objectFormatter, String separator);
}
}