Render decimal numbers in a table using current culture (#17650)

This commit is contained in:
Steve Lee
2022-07-18 14:27:06 -07:00
committed by GitHub
parent bcafe74c17
commit 08baf27b80
5 changed files with 98 additions and 28 deletions
@@ -361,6 +361,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
internal sealed class FieldFormattingDirective
{
internal string formatString = null; // optional
internal bool isTable = false;
}
#endregion Elementary Tokens
@@ -483,6 +483,12 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
directive = activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective;
}
if (directive is null)
{
directive = new FieldFormattingDirective();
directive.isTable = true;
}
fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, this.activeAssociationList[k].ResolvedExpression, directive);
tre.formatPropertyFieldList.Add(fpf);
}
@@ -208,8 +208,9 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
/// <param name="expressionFactory">Expression factory to create PSPropertyExpression.</param>
/// <param name="enumerationLimit">Limit on IEnumerable enumeration.</param>
/// <param name="formatErrorObject">Stores errors during string conversion.</param>
/// <param name="formatFloat">Determine if to format floating point numbers using current culture.</param>
/// <returns>String representation.</returns>
internal static string SmartToString(PSObject so, PSPropertyExpressionFactory expressionFactory, int enumerationLimit, StringFormatError formatErrorObject)
internal static string SmartToString(PSObject so, PSPropertyExpressionFactory expressionFactory, int enumerationLimit, StringFormatError formatErrorObject, bool formatFloat = false)
{
if (so == null)
return string.Empty;
@@ -294,7 +295,23 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
return sb.ToString();
}
// take care of the case there is no base object
if (formatFloat && so.BaseObject is not null)
{
// format numbers using the current culture
if (so.BaseObject is double dbl)
{
return dbl.ToString("F");
}
else if (so.BaseObject is float f)
{
return f.ToString("F");
}
else if (so.BaseObject is decimal d)
{
return d.ToString("F");
}
}
return so.ToString();
}
catch (Exception e) when (e is ExtendedTypeSystemException || e is InvalidOperationException)
@@ -333,43 +350,49 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
StringFormatError formatErrorObject, PSPropertyExpressionFactory expressionFactory)
{
PSObject so = PSObjectHelper.AsPSObject(val);
if (directive != null && !string.IsNullOrEmpty(directive.formatString))
bool isTable = false;
if (directive is not null)
{
// we have a formatting directive, apply it
// NOTE: with a format directive, we do not make any attempt
// to deal with IEnumerable
try
isTable = directive.isTable;
if (!string.IsNullOrEmpty(directive.formatString))
{
// use some heuristics to determine if we have "composite formatting"
// 2004/11/16-JonN This is heuristic but should be safe enough
if (directive.formatString.Contains("{0") || directive.formatString.Contains('}'))
// we have a formatting directive, apply it
// NOTE: with a format directive, we do not make any attempt
// to deal with IEnumerable
try
{
// we do have it, just use it
return string.Format(CultureInfo.CurrentCulture, directive.formatString, so);
// use some heuristics to determine if we have "composite formatting"
// 2004/11/16-JonN This is heuristic but should be safe enough
if (directive.formatString.Contains("{0") || directive.formatString.Contains('}'))
{
// we do have it, just use it
return string.Format(CultureInfo.CurrentCulture, directive.formatString, so);
}
// we fall back to the PSObject's IFormattable.ToString()
// pass a null IFormatProvider
return so.ToString(directive.formatString, formatProvider: null);
}
// we fall back to the PSObject's IFormattable.ToString()
// pass a null IFormatProvider
return so.ToString(directive.formatString, null);
}
catch (Exception e) // 2004/11/17-JonN This covers exceptions thrown in
// string.Format and PSObject.ToString().
// I think we can swallow these.
{
// NOTE: we catch all the exceptions, since we do not know
// what the underlying object access would throw
if (formatErrorObject != null)
catch (Exception e) // 2004/11/17-JonN This covers exceptions thrown in
// string.Format and PSObject.ToString().
// I think we can swallow these.
{
formatErrorObject.sourceObject = so;
formatErrorObject.exception = e;
formatErrorObject.formatString = directive.formatString;
return string.Empty;
// NOTE: we catch all the exceptions, since we do not know
// what the underlying object access would throw
if (formatErrorObject is not null)
{
formatErrorObject.sourceObject = so;
formatErrorObject.exception = e;
formatErrorObject.formatString = directive.formatString;
return string.Empty;
}
}
}
}
// we do not have a formatting directive or we failed the formatting (fallback)
// but we did not report as an error;
// this call would deal with IEnumerable if the object implements it
return PSObjectHelper.SmartToString(so, expressionFactory, enumerationLimit, formatErrorObject);
return PSObjectHelper.SmartToString(so, expressionFactory, enumerationLimit, formatErrorObject, isTable);
}
private static PSMemberSet MaskDeserializedAndGetStandardMembers(PSObject so)
@@ -209,6 +209,27 @@ dbda : KM
$actual = $actual -replace "`r`n", "`n"
$actual | Should -BeExactly $expected
}
It 'Float, double, and decimal should not be truncated to number of decimals from current culture' {
$o = [PSCustomObject]@{
double = [double]1234.56789
float = [float]9876.543
decimal = [decimal]4567.123456789
}
$expected = @"
double : 1234.56789
float : 9876.543
decimal : 4567.123456789
"@
$actual = $o | Format-List | Out-String
($actual.Replace("`r`n", "`n")) | Should -BeExactly ($expected.Replace("`r`n", "`n"))
}
}
Describe 'Format-List color tests' {
@@ -845,6 +845,25 @@ A Name B
$actual = $obj | Format-Table | Out-String
($actual.Replace("`r`n", "`n")) | Should -BeExactly ($expected.Replace("`r`n", "`n"))
}
It 'Table should format floats, doubles, and decimals with number of decimals from current culture' {
$o = [PSCustomObject]@{
double = [double]1234.56789
float = [float]9876.54321
decimal = [decimal]4567.123456789
}
$table = $o | Format-Table | Out-String
$line = foreach ($line in $table.split([System.Environment]::NewLine)) { if ($line -match '^1234') { $line } }
$line | Should -Not -BeNullOrEmpty
$expectedDecimals = (Get-Culture).NumberFormat.NumberDecimalDigits
foreach ($num in $line.split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)) {
$numDecimals = $num.length - $num.indexOf((Get-Culture).NumberFormat.NumberDecimalSeparator) - 1
$numDecimals | Should -Be $expectedDecimals -Because $num
}
}
}
Describe 'Table color tests' {