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 2 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 FormatForDisplay(System.Collections.Generic.IEnumerable<> collection);
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure I would call the methods FormatForDisplay. How about Humanize?

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

public class DefaultFormatter
{
public DefaultFormatter() { }
Expand Down
1 change: 1 addition & 0 deletions src/Humanizer.Tests/Humanizer.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<Compile Include="Localisation\da\TimeSpanHumanizeTests.cs" />
<Compile Include="Localisation\de\NumberToWordsTests.cs" />
<Compile Include="Localisation\de\OrdinalizeTests.cs" />
<Compile Include="Localisation\en\EnglishCollectionFormatterTests.cs" />
<Compile Include="Localisation\es\OrdinalizeTests.cs" />
<Compile Include="Localisation\fr-BE\DateHumanizeTests.cs" />
<Compile Include="Localisation\fr-BE\TimeSpanHumanizeTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using Xunit;

namespace Humanizer.Tests.Localisation.en
{
public class EnglishCollectionFormatterTests : AmbientCulture
Copy link
Member

Choose a reason for hiding this comment

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

I think we should call this CollectionHumanizeTests and it should live on the root of the test project. The English tests are in the root.

{
public EnglishCollectionFormatterTests() : base("en") { }

[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"));
Copy link
Member

Choose a reason for hiding this comment

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

Nice!

}
}
}
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.FormatForDisplay(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.FormatForDisplay(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.FormatForDisplay(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.FormatForDisplay(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 FormatForDisplay<T>(IEnumerable<T> collection)
{
return FormatForDisplay(collection, o => o.ToString(), DefaultSeparator);
}

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

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

public virtual string FormatForDisplay<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,39 @@
using System;
using System.Collections.Generic;
using System.Linq;

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

public override string FormatForDisplay<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}";

separator = separator.Trim();
Copy link
Collaborator

Choose a reason for hiding this comment

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

why is it necessary to trim the provided separator?
Currently there is no test for this line, it could be removed without breaking any present tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd agree, removed in justin-edwards@27583dd


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 FormatForDisplay<T>(IEnumerable<T> collection);

/// <summary>
/// Formats the collection for display, calling `objectFormatter` on each object.
/// </summary>
/// <returns></returns>
String FormatForDisplay<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 FormatForDisplay<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 FormatForDisplay<T>(IEnumerable<T> collection, Func<T, String> objectFormatter, String separator);
}
}