Created
December 14, 2018 18:20
-
-
Save erica/26caaa0984ad8ffe7a98cb6d71596363 to your computer and use it in GitHub Desktop.
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
// More String Interpolation | |
public extension String { | |
/// The direction from which a string floats when | |
/// padded with repeated characters. | |
enum PaddingDirection { case left, right, center } | |
/// Returns a new string repeatedly padded with a character on | |
/// either or both sides of the source string. | |
/// | |
/// - Returns: A padded string | |
func padded( | |
_ direction: PaddingDirection, | |
with character: Character, | |
toWidth targetWidth: Int = 0 | |
) -> String { | |
var width = targetWidth | |
// Default the padding to one pad on each side | |
if targetWidth == 0 { | |
switch direction { | |
case .left, .right: width = count + 1 | |
case .center: width = count + 2 | |
} | |
} | |
// Determine core padding required | |
guard width > count else { return self } | |
let corePadCount = width - count | |
func padding(_ count: Int) -> String { | |
return String(repeating: character, count: count) | |
} | |
switch direction { | |
case .left: | |
// Left pad "abc" to width 5 with " " -> " abc" | |
return padding(corePadCount) + self | |
case .right: | |
// Right pad "abc" to width 5 with " " -> "abc " | |
return self + padding(corePadCount) | |
case .center: | |
// Center pad "abc" to width 5 with " " -> " abc " | |
let halfPad = corePadCount / 2 | |
return padding(corePadCount - halfPad) + self + padding(halfPad) | |
} | |
} | |
} | |
public extension String.StringInterpolation { | |
/// Return character-padded version of the value | |
mutating func appendInterpolation<T>(_ value: T, width: Int, direction: String.PaddingDirection, pad: Character = " ") { | |
appendLiteral("\(value)".padded(direction, with: pad, toWidth: width)) | |
} | |
} | |
"\(24.5, width: 6, direction: .right, pad: "0")" // 24.500 | |
"\("Hello", width: 10, direction: .center)" // " Hello " | |
public extension BinaryInteger { | |
/// Returns front-padded integer with leading zeroes | |
func stringFrontPadded(to count: Int, radix: Int = 10) -> String { | |
var string = String(self, radix: radix) | |
while string.count < count { | |
string = "0" + string | |
} | |
return string | |
} | |
} | |
public extension String.StringInterpolation { | |
/// Standard binary prefix | |
static let binaryPrefix = "0b" | |
/// Standard octal prefix | |
static let octalPrefix = "0o" | |
/// Standard hex prefix | |
static let hexPrefix = "0x" | |
/// Return padded version of the value | |
mutating func appendInterpolation<Number: BinaryInteger>(_ value: Number, toWidth width: Int = 0, radix: Int = 10, prefix: Bool = false) { | |
var prefixString = "" | |
if prefix { | |
switch radix { | |
case 2: | |
prefixString += String.StringInterpolation.binaryPrefix | |
case 8: | |
prefixString += String.StringInterpolation.octalPrefix | |
case 16: | |
prefixString += String.StringInterpolation.hexPrefix | |
default: | |
break | |
} | |
} | |
let valueString = value.stringFrontPadded(to: width, radix: radix) | |
appendInterpolation(prefixString + valueString) | |
} | |
} | |
"\(0x2A, toWidth:6, radix: 16, prefix: true)" // 0x00002a | |
"\(0x2A, toWidth:6, radix: 16)" // 00002a | |
"\(42, toWidth:6, radix: 16)" // 00002a | |
import QuartzCore | |
/// Numbers that can be represented as Double instances | |
public protocol DoubleRepresentable { | |
/// Returns self as a `Double` value | |
var doubleValue: Double { get } | |
} | |
/// Numbers that convert to other types | |
public protocol ConvertibleNumberType: DoubleRepresentable { | |
/// Returns self as a `Float` value | |
var floatValue: Float { get } | |
/// Returns self as an `Int` value | |
var intValue: Int { get } | |
/// Returns self as a `CGFloat` value | |
var cgFloatValue: CGFloat { get } | |
} | |
// Default conversion implementations | |
public extension ConvertibleNumberType { | |
/// Returns self as a `Float` value | |
var floatValue: Float { get { return Float(doubleValue) } } | |
/// Returns self as an `Int` value | |
var intValue: Int { get { return lrint(doubleValue) } } | |
/// Returns self as a `CGFloat` value | |
var cgFloatValue: CGFloat { get { return CGFloat(doubleValue) } } | |
} | |
// Conform to ConvertibleNumberType | |
extension Double: ConvertibleNumberType { | |
/// Returns self as a double value | |
public var doubleValue: Double { return self } | |
} | |
// Conform to ConvertibleNumberType | |
extension Int: ConvertibleNumberType { | |
/// Returns self as a double value | |
public var doubleValue: Double { return Double(self) } | |
} | |
// Conform to ConvertibleNumberType | |
extension CGFloat: ConvertibleNumberType { | |
/// Returns self as a double value | |
public var doubleValue: Double { return Double(self) } | |
} | |
// Conform to ConvertibleNumberType | |
extension Float: ConvertibleNumberType { | |
/// Returns self as a double value | |
public var doubleValue: Double { return Double(self) } | |
} | |
public extension ConvertibleNumberType { | |
/// Pad a number to a set number of digits after the decimal | |
/// point without rounding. | |
func toPrecision(digits: Int) -> String { | |
// Zero digits omits decimal point to return Int representation | |
guard digits != 0 else { | |
return "\(Int(floor(self.doubleValue)))" | |
} | |
// Convert to double and check for "." | |
var string = "\(self.doubleValue)" | |
guard let range = string.range(of: ".") | |
else { return string } | |
// Truncate | |
let distance = string.distance(from: range.lowerBound, to: string.endIndex) | |
if distance > digits { | |
return String(string.prefix(string.count - (distance - (digits + 1)))) | |
} | |
// Pad | |
while string.distance(from: range.lowerBound, to: string.endIndex) <= digits { | |
string += "0" | |
} | |
return string | |
} | |
} | |
public extension String.StringInterpolation { | |
/// Return decimal-padded version of the value | |
mutating func appendInterpolation<Number: ConvertibleNumberType>(_ value: Number, places: Int) { | |
appendInterpolation(value.toPrecision(digits: places)) | |
} | |
} | |
"\(50, places:0)" // 50 | |
"\(50, places:1)" // 50.0 | |
"\(Double.pi, places:1)" // 3.1 | |
"\(Double.pi, places:2)" // 3.14 | |
"\(Double.pi, places:6)" // 3.141592 | |
"\(Double.pi, places:20)" // 3.14159265358979300000 | |
"\(49.9837, places:0)" // 49 | |
"\(49.9837, places:1)" // 49.9 | |
"\(49.9837, places:2)" // 49.98 | |
"\(49.9837, places:3)" // 49.983 | |
"\(49.9837, places:4)" // 49.9837 | |
"\(49.9837, places:5)" // 49.98370 | |
"\(49.9837, places:8)" // 49.98370000 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment