Skip to content

Commit

Permalink
Merge pull request #566 from jnm2/improve-collections
Browse files Browse the repository at this point in the history
Handling of null, blank and untrimmed collection items
  • Loading branch information
hazzik authored Jul 3, 2016
2 parents 5b6b855 + 06c3686 commit dea5619
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 40 deletions.
24 changes: 13 additions & 11 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ TimeSpan.FromMilliseconds(1299630020).Humanize(3, collectionSeparator: null) =>
```

### <a id="humanize-collections">Humanize Collections</a>
You can call `Humanize` on any `IEnumerable` to get a nicely formatted string representing the objects in the collection. By default `ToString()` will be called on each item to get its representation but a formatting function may be passed to `Humanize` instead. Additionally, a default separator is provided("and" in English), but a different separator may be passed into `Humanize`.
You can call `Humanize` on any `IEnumerable` to get a nicely formatted string representing the objects in the collection. By default `ToString()` will be called on each item to get its representation but a formatting function may be passed to `Humanize` instead. Additionally, a default separator is provided ("and" in English), but a different separator may be passed into `Humanize`.

For instance:

Expand All @@ -458,24 +458,26 @@ class SomeClass

string FormatSomeClass(SomeClass sc)
{
return string.Format("SomeObject #{0} - {1}", sc.SomeInt, sc.SomeString);
return string.Format("SomeObject #{0} - {1}", sc.SomeInt, sc.SomeString);
}

var collection = new List<SomeClass>
{
new SomeClass { SomeInt = 1, SomeString = "One" },
new SomeClass { SomeInt = 2, SomeString = "Two" },
new SomeClass { SomeInt = 3, SomeString = "Three" }
};
{
new SomeClass { SomeInt = 1, SomeString = "One" },
new SomeClass { SomeInt = 2, SomeString = "Two" },
new SomeClass { SomeInt = 3, SomeString = "Three" }
};

collection.Humanize() // "Specific String, Specific String, and Specific String"
collection.Humanize("or") //"Specific String, Specific String, or Specific String"
collection.Humanize(FormatSomeClass) //"SomeObject #1 - One, SomeObject #2 - Two, and SomeObject #3 - Three"
collection.Humanize(sc => sc.SomeInt.Ordinalize(), "or") //"1st, 2nd, or 3rd"
collection.Humanize("or") // "Specific String, Specific String, or Specific String"
collection.Humanize(FormatSomeClass) // "SomeObject #1 - One, SomeObject #2 - Two, and SomeObject #3 - Three"
collection.Humanize(sc => sc.SomeInt.Ordinalize(), "or") // "1st, 2nd, or 3rd"
```

You can provide your own collection formatter by implementing `ICollectionFormatter` and registering it with `Configurator.CollectionFormatters`
Items are trimmed and blank (NullOrWhitespace) items are skipped. This results in clean comma punctuation. (If there is a custom formatter function, this applies only to the formatter's output.)

You can provide your own collection formatter by implementing `ICollectionFormatter` and registering it with `Configurator.CollectionFormatters`.

### <a id="inflector-methods">Inflector methods</a>
There are also a few inflector methods:

Expand Down
36 changes: 36 additions & 0 deletions src/Humanizer.Tests.Shared/CollectionHumanizeTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using Xunit;

Expand Down Expand Up @@ -87,5 +88,40 @@ public void HumanizeUsesObjectFormatterWhenSeparatorIsProvided()
var humanized = _testCollection.Humanize(sc => string.Format("SomeObject #{0} - {1}", sc.SomeInt, sc.SomeString), "or");
Assert.Equal("SomeObject #1 - One, SomeObject #2 - Two, or SomeObject #3 - Three", humanized);
}

[Fact]
public void HumanizeHandlesNullItemsWithoutAnException()
{
Assert.Null(Record.Exception(() => new object[] { null, null }.Humanize()));
}

[Fact]
public void HumanizeHandlesNullFormatterReturnsWithoutAnException()
{
Assert.Null(Record.Exception(() => new[] { "A", "B", "C" }.Humanize(_ => null)));
}

[Fact]
public void HumanizeRunsFormatterOnNulls()
{
Assert.Equal("1, (null), and 3", new int?[] { 1, null, 3 }.Humanize(_ => _?.ToString() ?? "(null)"));
}

[Fact]
public void HumanizeRemovesEmptyItemsByDefault()
{
Assert.Equal("A and C", new[] { "A", " ", "C" }.Humanize(dummyFormatter));
}

[Fact]
public void HumanizeTrimsItemsByDefault()
{
Assert.Equal("A, B, and C", new[] { "A", " B ", "C" }.Humanize(dummyFormatter));
}

/// <summary>
/// Use the dummy formatter to ensure tests are testing formatter output rather than input
/// </summary>
private static readonly Func<string, string> dummyFormatter = input => input;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public DefaultCollectionFormatter(string defaultSeparator)

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

public virtual string Humanize<T>(IEnumerable<T> collection, Func<T, string> objectFormatter)
Expand All @@ -25,31 +25,37 @@ public virtual string Humanize<T>(IEnumerable<T> collection, Func<T, string> obj

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

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

var itemsArray = collection as T[] ?? collection.ToArray();
var items = collection
.Select(objectFormatter)
.Select(item => item == null ? string.Empty : item.Trim())
.Where(item => !string.IsNullOrWhiteSpace(item));

var itemsArray = items.ToArray();
var count = itemsArray.Length;

if (count == 0)
return "";

if (count == 1)
return objectFormatter(itemsArray[0]);
return itemsArray[0];

var itemsBeforeLast = itemsArray.Take(count - 1);
var lastItem = itemsArray.Skip(count - 1).First();

return string.Format("{0} {1} {2}",
string.Join(", ", itemsBeforeLast.Select(objectFormatter)),
return string.Format(GetConjunctionFormatString(count),
string.Join(", ", itemsBeforeLast),
separator,
objectFormatter(lastItem));
lastItem);
}

protected virtual string GetConjunctionFormatString(int itemCount) => "{0} {1} {2}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,6 @@ public OxfordStyleCollectionFormatter(string defaultSeparator)
{
}

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();

var count = enumerable.Count();

if (count == 0)
return "";

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

var 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()));
}
protected override string GetConjunctionFormatString(int itemCount) => itemCount > 2 ? "{0}, {1} {2}" : "{0} {1} {2}";
}
}

0 comments on commit dea5619

Please sign in to comment.