Created
September 9, 2023 13:33
-
-
Save ole/dc26557165fbccbf80587504db29ed4c to your computer and use it in GitHub Desktop.
An "extension" of FloatingPointFormatStyle that adds a `minusSign` API to customize the character(s) used as the minus sign. You could add additional extensions by following the same pattern. This demonstrates that "extending" the built-in format styles requires a lot of boilerplate because you have to replicate their APIs in your own type.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// An "extension" of FloatingPointFormatStyle that adds a `minusSign` API to customize | |
/// the character(s) used as the minus sign. | |
/// | |
/// You could add additional extensions by following the same pattern. | |
/// | |
/// All other APIs are copied from FloatingPointFormatStyle and forward to it for their | |
/// implementation. This isn’t a full replica of the FloatingPointFormatStyle API, though, | |
/// because it’s only intended as a proof of concept. | |
@available(macOS 12.0, *) | |
public struct ExtensibleFloatingPointFormatStyle<Value> | |
where Value : BinaryFloatingPoint | |
{ | |
public var base: FloatingPointFormatStyle<Value> | |
public var minusSign: Optional<String> | |
public init(locale: Locale = .autoupdatingCurrent) { | |
self.base = .init(locale: locale) | |
self.minusSign = nil | |
} | |
private init(base: FloatingPointFormatStyle<Value>, minusSign: String?) { | |
self.base = base | |
self.minusSign = minusSign | |
} | |
// MARK: - New APIs | |
public func minusSign(_ sign: String) -> Self { | |
Self(base: base, minusSign: sign) | |
} | |
// MARK: - Replicating FloatingPointFormatStyle APIs | |
public func locale(_ locale: Locale) -> Self { | |
Self(base: base.locale(locale), minusSign: minusSign) | |
} | |
public func grouping(_ group: FloatingPointFormatStyle<Value>.Configuration.Grouping) -> Self { | |
Self(base: base.grouping(group), minusSign: minusSign) | |
} | |
public func precision(_ p: FloatingPointFormatStyle<Value>.Configuration.Precision) -> Self { | |
Self(base: base.precision(p), minusSign: minusSign) | |
} | |
public func sign(strategy: FloatingPointFormatStyle<Value>.Configuration.SignDisplayStrategy) -> Self { | |
Self(base: base.sign(strategy: strategy), minusSign: minusSign) | |
} | |
public func decimalSeparator(strategy: FloatingPointFormatStyle<Value>.Configuration.DecimalSeparatorDisplayStrategy) -> Self { | |
Self(base: base.decimalSeparator(strategy: strategy), minusSign: minusSign) | |
} | |
public func rounded(rule: FloatingPointFormatStyle<Value>.Configuration.RoundingRule = .toNearestOrEven, increment: Double? = nil) -> Self { | |
Self(base: base.rounded(rule: rule, increment: increment), minusSign: minusSign) | |
} | |
public func scale(_ multiplicand: Double) -> Self { | |
Self(base: base.scale(multiplicand), minusSign: minusSign) | |
} | |
public func notation(_ notation: FloatingPointFormatStyle<Value>.Configuration.Notation) -> Self { | |
Self(base: base.notation(notation), minusSign: minusSign) | |
} | |
} | |
extension ExtensibleFloatingPointFormatStyle : FormatStyle { | |
public typealias FormatInput = Value | |
public typealias FormatOutput = String | |
public func format(_ value: Value) -> String { | |
// Format base | |
var result = base.format(value) | |
// Replace minus sign if necessary. | |
// | |
// Warning: This assumes that the character "-" (ASCII hyphen-minus) doesn’t occur | |
// in the string except for the minus sign. This assumption is problematic. | |
// A better implementation could use the result of `base.attributed` to identify the | |
// sign reliably (it should have the attribute | |
// `AttributeScopes.FoundationAttributes.NumberFormatAttributes.SymbolAttribute.Symbol.sign`). | |
if let minusSign = minusSign { | |
result.replace("-", with: minusSign) | |
} | |
return result | |
} | |
} | |
extension FormatStyle where Self == ExtensibleFloatingPointFormatStyle<Double> { | |
public static var extNumber: ExtensibleFloatingPointFormatStyle<Double> { | |
ExtensibleFloatingPointFormatStyle() | |
} | |
} | |
extension FormatStyle where Self == ExtensibleFloatingPointFormatStyle<Float> { | |
public static var extNumber: ExtensibleFloatingPointFormatStyle<Float> { | |
ExtensibleFloatingPointFormatStyle() | |
} | |
} | |
// MARK: - Usage | |
let locale = Locale(identifier: "en_US") | |
let number = -123456.789 | |
print(number.formatted(.number.locale(locale))) | |
print(number.formatted(.extNumber.locale(locale))) | |
// Test that the `.minusSign` API works. | |
print(number.formatted(.extNumber.locale(locale).minusSign("\u{2212}"))) | |
print(number.formatted(.extNumber.locale(locale).minusSign("–––"))) | |
// Test that the `.minusSign` API can be combined in arbitrary order with FloatingPointFormatStyle APIs. | |
print(number.formatted(.extNumber.locale(locale).grouping(.never).minusSign("\u{2212}").precision(.fractionLength(1)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment