├── .gitignore ├── Formatting.playground ├── Contents.swift └── contents.xcplayground ├── Formatting.xcodeproj ├── Configs │ └── Project.xcconfig ├── FormattingTestSuite_Info.plist ├── Formatting_Info.plist ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Formatting.xcscheme │ └── xcschememanagement.plist ├── LICENSE.txt ├── Package.swift ├── README.md ├── Sources ├── Date.swift ├── Formatter.swift ├── Formatters.swift ├── Operators.swift └── Uncurry.swift └── Tests ├── Formatting └── FormattingTests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | *.moved-aside 17 | DerivedData 18 | *.hmap 19 | *.ipa 20 | *.xcuserstate 21 | -------------------------------------------------------------------------------- /Formatting.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Formatting 2 | import Foundation 3 | 4 | 5 | 6 | format("Hello, " % string % "! It's " % K % " o'clock.", "world", Date()) 7 | 8 | 9 | 10 | let dateFormatter = 11 | format(yyyy % "-" <> MM % "-" <> dd) 12 | 13 | dateFormatter(Date()) 14 | 15 | 16 | 17 | let longDateFormatter = 18 | format(date(.long)) 19 | 20 | longDateFormatter(Date()) 21 | 22 | 23 | 24 | format(bytes)(1024) 25 | 26 | 27 | 28 | let logFormatter = 29 | format(fitLeft(5) .% right(5) % " -- [" % iso8601 % "] " % string) 30 | 31 | let infoLog = logFormatter("INFO") 32 | let debugLog = logFormatter("DEBUG") 33 | 34 | print(infoLog(Date())("Logging in...")) 35 | print(debugLog(Date())("Logged in successfully!")) 36 | -------------------------------------------------------------------------------- /Formatting.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Formatting.xcodeproj/Configs/Project.xcconfig: -------------------------------------------------------------------------------- 1 | PRODUCT_NAME = $(TARGET_NAME) 2 | SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator 3 | MACOSX_DEPLOYMENT_TARGET = 10.10 4 | DYLIB_INSTALL_NAME_BASE = @rpath 5 | OTHER_SWIFT_FLAGS = -DXcode 6 | COMBINE_HIDPI_IMAGES = YES 7 | USE_HEADERMAP = NO 8 | -------------------------------------------------------------------------------- /Formatting.xcodeproj/FormattingTestSuite_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Formatting.xcodeproj/Formatting_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Formatting.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 80F0A63E1D206A1900FFB48A /* Uncurry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80F0A63D1D206A1900FFB48A /* Uncurry.swift */; }; 11 | _LinkFileRef_Formatting_via_FormattingTestSuite /* Formatting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "_____Product_Formatting" /* Formatting.framework */; }; 12 | __src_cc_ref_Sources/Date.swift /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/Date.swift /* Date.swift */; }; 13 | __src_cc_ref_Sources/Formatter.swift /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/Formatter.swift /* Formatter.swift */; }; 14 | __src_cc_ref_Sources/Formatters.swift /* Formatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/Formatters.swift /* Formatters.swift */; }; 15 | __src_cc_ref_Sources/Operators.swift /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/Operators.swift /* Operators.swift */; }; 16 | __src_cc_ref_Tests/Formatting/FormattingTests.swift /* FormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Tests/Formatting/FormattingTests.swift /* FormattingTests.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 80F0A63B1D2040E400FFB48A /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = __RootObject_ /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = "______Target_Formatting"; 25 | remoteInfo = Formatting; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 80F0A63C1D2040F100FFB48A /* Formatting.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Formatting.playground; sourceTree = ""; }; 31 | 80F0A63D1D206A1900FFB48A /* Uncurry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Uncurry.swift; sourceTree = ""; }; 32 | 80F5CAA81D2553CB00625455 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 33 | 80F5CAAA1D2553D100625455 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; 34 | __PBXFileRef_Formatting.xcodeproj/Configs/Project.xcconfig /* Project.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Project.xcconfig; path = Formatting.xcodeproj/Configs/Project.xcconfig; sourceTree = ""; }; 35 | __PBXFileRef_FormattingTestSuite_Info.plist /* FormattingTestSuite_Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = FormattingTestSuite_Info.plist; path = Formatting.xcodeproj/FormattingTestSuite_Info.plist; sourceTree = SOURCE_ROOT; }; 36 | __PBXFileRef_Formatting_Info.plist /* Formatting_Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Formatting_Info.plist; path = Formatting.xcodeproj/Formatting_Info.plist; sourceTree = SOURCE_ROOT; }; 37 | __PBXFileRef_Package.swift /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 38 | __PBXFileRef_Sources/Date.swift /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; 39 | __PBXFileRef_Sources/Formatter.swift /* Formatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formatter.swift; sourceTree = ""; }; 40 | __PBXFileRef_Sources/Formatters.swift /* Formatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formatters.swift; sourceTree = ""; }; 41 | __PBXFileRef_Sources/Operators.swift /* Operators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; 42 | __PBXFileRef_Tests/Formatting/FormattingTests.swift /* FormattingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattingTests.swift; sourceTree = ""; }; 43 | "_____Product_Formatting" /* Formatting.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Formatting.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | "_____Product_FormattingTestSuite" /* FormattingTestSuite.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = FormattingTestSuite.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | "___LinkPhase_Formatting" /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 0; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | "___LinkPhase_FormattingTestSuite" /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 0; 58 | files = ( 59 | _LinkFileRef_Formatting_via_FormattingTestSuite /* Formatting.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | TestProducts_ /* Tests */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | "_____Product_FormattingTestSuite" /* FormattingTestSuite.xctest */, 70 | ); 71 | name = Tests; 72 | sourceTree = ""; 73 | }; 74 | "___RootGroup_" = { 75 | isa = PBXGroup; 76 | children = ( 77 | 80F5CAA81D2553CB00625455 /* README.md */, 78 | 80F5CAAA1D2553D100625455 /* LICENSE.txt */, 79 | 80F0A63C1D2040F100FFB48A /* Formatting.playground */, 80 | __PBXFileRef_Package.swift /* Package.swift */, 81 | "_____Configs_" /* Configs */, 82 | "_____Sources_" /* Sources */, 83 | "_______Tests_" /* Tests */, 84 | "____Products_" /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | "____Products_" /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | TestProducts_ /* Tests */, 92 | "_____Product_Formatting" /* Formatting.framework */, 93 | ); 94 | name = Products; 95 | sourceTree = ""; 96 | }; 97 | "_____Configs_" /* Configs */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | __PBXFileRef_Formatting.xcodeproj/Configs/Project.xcconfig /* Project.xcconfig */, 101 | ); 102 | name = Configs; 103 | sourceTree = ""; 104 | }; 105 | "_____Sources_" /* Sources */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | "_______Group_Formatting" /* Formatting */, 109 | ); 110 | name = Sources; 111 | sourceTree = ""; 112 | }; 113 | "_______Group_Formatting" /* Formatting */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | __PBXFileRef_Sources/Date.swift /* Date.swift */, 117 | __PBXFileRef_Sources/Formatter.swift /* Formatter.swift */, 118 | __PBXFileRef_Sources/Formatters.swift /* Formatters.swift */, 119 | __PBXFileRef_Sources/Operators.swift /* Operators.swift */, 120 | 80F0A63D1D206A1900FFB48A /* Uncurry.swift */, 121 | __PBXFileRef_Formatting_Info.plist /* Formatting_Info.plist */, 122 | ); 123 | name = Formatting; 124 | path = Sources; 125 | sourceTree = ""; 126 | }; 127 | "_______Group_FormattingTestSuite" /* FormattingTestSuite */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | __PBXFileRef_Tests/Formatting/FormattingTests.swift /* FormattingTests.swift */, 131 | __PBXFileRef_FormattingTestSuite_Info.plist /* FormattingTestSuite_Info.plist */, 132 | ); 133 | name = FormattingTestSuite; 134 | path = Tests/Formatting; 135 | sourceTree = ""; 136 | }; 137 | "_______Tests_" /* Tests */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | "_______Group_FormattingTestSuite" /* FormattingTestSuite */, 141 | ); 142 | name = Tests; 143 | sourceTree = ""; 144 | }; 145 | /* End PBXGroup section */ 146 | 147 | /* Begin PBXNativeTarget section */ 148 | "______Target_Formatting" /* Formatting */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = "_______Confs_Formatting" /* Build configuration list for PBXNativeTarget "Formatting" */; 151 | buildPhases = ( 152 | CompilePhase_Formatting /* Sources */, 153 | "___LinkPhase_Formatting" /* Frameworks */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = Formatting; 160 | productName = Formatting; 161 | productReference = "_____Product_Formatting" /* Formatting.framework */; 162 | productType = "com.apple.product-type.framework"; 163 | }; 164 | "______Target_FormattingTestSuite" /* FormattingTestSuite */ = { 165 | isa = PBXNativeTarget; 166 | buildConfigurationList = "_______Confs_FormattingTestSuite" /* Build configuration list for PBXNativeTarget "FormattingTestSuite" */; 167 | buildPhases = ( 168 | CompilePhase_FormattingTestSuite /* Sources */, 169 | "___LinkPhase_FormattingTestSuite" /* Frameworks */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | __Dependency_Formatting /* PBXTargetDependency */, 175 | ); 176 | name = FormattingTestSuite; 177 | productName = FormattingTestSuite; 178 | productReference = "_____Product_FormattingTestSuite" /* FormattingTestSuite.xctest */; 179 | productType = "com.apple.product-type.bundle.unit-test"; 180 | }; 181 | /* End PBXNativeTarget section */ 182 | 183 | /* Begin PBXProject section */ 184 | __RootObject_ /* Project object */ = { 185 | isa = PBXProject; 186 | attributes = { 187 | LastUpgradeCheck = 9999; 188 | }; 189 | buildConfigurationList = "___RootConfs_" /* Build configuration list for PBXProject "Formatting" */; 190 | compatibilityVersion = "Xcode 3.2"; 191 | developmentRegion = English; 192 | hasScannedForEncodings = 0; 193 | knownRegions = ( 194 | en, 195 | ); 196 | mainGroup = "___RootGroup_"; 197 | productRefGroup = "____Products_" /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | "______Target_Formatting" /* Formatting */, 202 | "______Target_FormattingTestSuite" /* FormattingTestSuite */, 203 | ); 204 | }; 205 | /* End PBXProject section */ 206 | 207 | /* Begin PBXSourcesBuildPhase section */ 208 | CompilePhase_Formatting /* Sources */ = { 209 | isa = PBXSourcesBuildPhase; 210 | buildActionMask = 0; 211 | files = ( 212 | __src_cc_ref_Sources/Date.swift /* Date.swift in Sources */, 213 | __src_cc_ref_Sources/Formatter.swift /* Formatter.swift in Sources */, 214 | 80F0A63E1D206A1900FFB48A /* Uncurry.swift in Sources */, 215 | __src_cc_ref_Sources/Formatters.swift /* Formatters.swift in Sources */, 216 | __src_cc_ref_Sources/Operators.swift /* Operators.swift in Sources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | CompilePhase_FormattingTestSuite /* Sources */ = { 221 | isa = PBXSourcesBuildPhase; 222 | buildActionMask = 0; 223 | files = ( 224 | __src_cc_ref_Tests/Formatting/FormattingTests.swift /* FormattingTests.swift in Sources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | /* End PBXSourcesBuildPhase section */ 229 | 230 | /* Begin PBXTargetDependency section */ 231 | __Dependency_Formatting /* PBXTargetDependency */ = { 232 | isa = PBXTargetDependency; 233 | target = "______Target_Formatting" /* Formatting */; 234 | targetProxy = 80F0A63B1D2040E400FFB48A /* PBXContainerItemProxy */; 235 | }; 236 | /* End PBXTargetDependency section */ 237 | 238 | /* Begin XCBuildConfiguration section */ 239 | _ReleaseConf_Formatting /* Release */ = { 240 | isa = XCBuildConfiguration; 241 | buildSettings = { 242 | ENABLE_TESTABILITY = YES; 243 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; 244 | INFOPLIST_FILE = Formatting.xcodeproj/Formatting_Info.plist; 245 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 246 | OTHER_LDFLAGS = "$(inherited)"; 247 | OTHER_SWIFT_FLAGS = "$(inherited)"; 248 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 249 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 250 | }; 251 | name = Release; 252 | }; 253 | _ReleaseConf_FormattingTestSuite /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 257 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; 258 | INFOPLIST_FILE = Formatting.xcodeproj/FormattingTestSuite_Info.plist; 259 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; 260 | OTHER_LDFLAGS = "$(inherited)"; 261 | OTHER_SWIFT_FLAGS = "$(inherited)"; 262 | }; 263 | name = Release; 264 | }; 265 | "___DebugConf_Formatting" /* Debug */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ENABLE_TESTABILITY = YES; 269 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; 270 | INFOPLIST_FILE = Formatting.xcodeproj/Formatting_Info.plist; 271 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 272 | OTHER_LDFLAGS = "$(inherited)"; 273 | OTHER_SWIFT_FLAGS = "$(inherited)"; 274 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 275 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 276 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 277 | }; 278 | name = Debug; 279 | }; 280 | "___DebugConf_FormattingTestSuite" /* Debug */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 284 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; 285 | INFOPLIST_FILE = Formatting.xcodeproj/FormattingTestSuite_Info.plist; 286 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; 287 | OTHER_LDFLAGS = "$(inherited)"; 288 | OTHER_SWIFT_FLAGS = "$(inherited)"; 289 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 290 | }; 291 | name = Debug; 292 | }; 293 | "_____Release_" /* Release */ = { 294 | isa = XCBuildConfiguration; 295 | baseConfigurationReference = __PBXFileRef_Formatting.xcodeproj/Configs/Project.xcconfig /* Project.xcconfig */; 296 | buildSettings = { 297 | SWIFT_VERSION = 3.0; 298 | }; 299 | name = Release; 300 | }; 301 | "_______Debug_" /* Debug */ = { 302 | isa = XCBuildConfiguration; 303 | baseConfigurationReference = __PBXFileRef_Formatting.xcodeproj/Configs/Project.xcconfig /* Project.xcconfig */; 304 | buildSettings = { 305 | SWIFT_VERSION = 3.0; 306 | }; 307 | name = Debug; 308 | }; 309 | /* End XCBuildConfiguration section */ 310 | 311 | /* Begin XCConfigurationList section */ 312 | "___RootConfs_" /* Build configuration list for PBXProject "Formatting" */ = { 313 | isa = XCConfigurationList; 314 | buildConfigurations = ( 315 | "_______Debug_" /* Debug */, 316 | "_____Release_" /* Release */, 317 | ); 318 | defaultConfigurationIsVisible = 0; 319 | defaultConfigurationName = Debug; 320 | }; 321 | "_______Confs_Formatting" /* Build configuration list for PBXNativeTarget "Formatting" */ = { 322 | isa = XCConfigurationList; 323 | buildConfigurations = ( 324 | "___DebugConf_Formatting" /* Debug */, 325 | _ReleaseConf_Formatting /* Release */, 326 | ); 327 | defaultConfigurationIsVisible = 0; 328 | defaultConfigurationName = Debug; 329 | }; 330 | "_______Confs_FormattingTestSuite" /* Build configuration list for PBXNativeTarget "FormattingTestSuite" */ = { 331 | isa = XCConfigurationList; 332 | buildConfigurations = ( 333 | "___DebugConf_FormattingTestSuite" /* Debug */, 334 | _ReleaseConf_FormattingTestSuite /* Release */, 335 | ); 336 | defaultConfigurationIsVisible = 0; 337 | defaultConfigurationName = Debug; 338 | }; 339 | /* End XCConfigurationList section */ 340 | }; 341 | rootObject = __RootObject_ /* Project object */; 342 | } 343 | -------------------------------------------------------------------------------- /Formatting.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Formatting.xcodeproj/xcshareddata/xcschemes/Formatting.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Formatting.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SchemeUserState 5 | 6 | Formatting.xcscheme 7 | 8 | 9 | SuppressBuildableAutocreation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2016 Stephen Celis () 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "Formatting" 5 | ) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Formatting 2 | 3 | Type-safe, functional string formatting in Swift. 4 | 5 | Inspired by Chris Done's excellent [Haskell library](https://github.com/chrisdone/formatting). 6 | 7 | ``` swift 8 | import Formatting 9 | 10 | format("Hello, " % string % "!", "world") 11 | // "Hello, world!" 12 | ``` 13 | 14 | 15 | ## Introduction 16 | 17 | Traditional string formatting methods (interpolation, `printf`, and template strings) can lead to subtle (and not so subtle) runtime bugs. 18 | 19 | ``` swift 20 | print("Hello, \(thing)!") // Hello, nil! 21 | print("Hello, \(thing)!") // Hello, Optional("world")! 22 | ``` 23 | 24 | ``` objective-c 25 | NSLog(@"Hello, %@!", thing); // Hello, (null)! 26 | ``` 27 | 28 | **Formatting** brings compile-time checks. 29 | 30 | ``` swift 31 | print("Hello, " % string % "!", thing) // Value of optional type String? not unwrapped 32 | ``` 33 | 34 | And composability. 35 | 36 | ``` swift 37 | let greet = 38 | format("Hello, " % string % "!") 39 | 40 | greet("world") // Hello, world! 41 | ``` 42 | 43 | 44 | 45 | ## Composing formatters 46 | 47 | Use `%` to build a formatter with strings and other formatters. 48 | 49 | ``` swift 50 | format(string % " is " % int % "years old.", "Alice", 25) 51 | // "Alice is 25 years old." 52 | ``` 53 | 54 | Use `<>` to pass the previous formatter argument to the next formatter. 55 | 56 | ``` swift 57 | format(yyyy % "-" <> MM % "-" <> dd, Date()) 58 | // "2016-06-28" 59 | ``` 60 | 61 | Use `.%` to feed the result of one formatter into another. 62 | 63 | ``` swift 64 | format(left(2, "0") .% hex, 10) 65 | // "0a" 66 | ``` 67 | 68 | Call `format` without arguments to return a curried formatter function. 69 | 70 | ``` swift 71 | let log = 72 | format(right(5) % " -- [" % iso8601 % "] " % string) 73 | 74 | let infoLog = log("INFO") 75 | let debugLog = log("DEBUG") 76 | 77 | infoLog(Date())("Logging in...") 78 | // "INFO -- [2016-06-28T12:34:56Z] Logging in..." 79 | debugLog(Date())("Logged in successfully!") 80 | // "DEBUG -- [2016-06-28T12:34:56Z] Logged in successfully!" 81 | ``` 82 | 83 | ## License 84 | 85 | Formatting is available under the MIT license. See [the LICENSE 86 | file](./LICENSE.txt) for more information. 87 | -------------------------------------------------------------------------------- /Sources/Date.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func date(_ formatter: DateFormatter) -> FormatterOf { 4 | return Formatter { f in { date in f(formatter.string(from: date)) } } 5 | } 6 | 7 | private func date(builder: (DateFormatter) -> ()) -> FormatterOf { 8 | let df = DateFormatter() 9 | builder(df) 10 | return date(df) 11 | } 12 | 13 | public func date(format: String) -> FormatterOf { 14 | return date { $0.dateFormat = format } 15 | } 16 | 17 | public func date(_ dateStyle: DateFormatter.Style, time timeStyle: DateFormatter.Style = .none) 18 | -> FormatterOf { 19 | assert(dateStyle != .none, "use the `time()` formatter for time") 20 | return date { ($0.dateStyle, $0.timeStyle) = (dateStyle, timeStyle) } 21 | } 22 | 23 | public func time(_ timeStyle: DateFormatter.Style) -> FormatterOf { 24 | return date(.none, time: timeStyle) 25 | } 26 | 27 | /** 28 | An ISO8601 formatter. 29 | 30 | - parameter options: formatting options 31 | */ 32 | @available(iOS 10, OSX 10.12, tvOS 10, watchOS 3, *) 33 | public func iso8601(_ options: ISO8601DateFormatter.Options = []) -> FormatterOf { 34 | let df = ISO8601DateFormatter() 35 | if !options.isEmpty { 36 | df.formatOptions = options 37 | } 38 | return Formatter { f in { date in f(df.string(from: date)) } } 39 | } 40 | 41 | /** 42 | An ISO8601 formatter with default `Foundation.ISO8601DateFormatter.Options`. 43 | */ 44 | @available(iOS 10, OSX 10.12, tvOS 10, watchOS 3, *) 45 | public func iso8601() -> FormatterOf { 46 | return iso8601([]) 47 | } 48 | 49 | // MARK: Era 50 | 51 | /** 52 | Era. Abbreviated form. 53 | 54 | format(G)(Date()) 55 | // "AD" 56 | */ 57 | public func G() -> FormatterOf { 58 | return date(format: "G") 59 | } 60 | 61 | /** 62 | Era. Long form. 63 | 64 | format(GGGG)(Date()) 65 | // "Anno Domini" 66 | */ 67 | public func GGGG() -> FormatterOf { 68 | return date(format: "GGGG") 69 | } 70 | 71 | /** 72 | Era. Narrow form. 73 | 74 | format(GGGGG)(Date()) 75 | // "A" 76 | */ 77 | public func GGGGG() -> FormatterOf { 78 | return date(format: "GGGGG") 79 | } 80 | 81 | // MARK: Year 82 | 83 | /** 84 | Year. 85 | 86 | format(y)(Date()) 87 | // "2016" 88 | */ 89 | public func y() -> FormatterOf { 90 | return date(format: "y") 91 | } 92 | 93 | /** 94 | Year. Zero-padded and truncated to 2 characters. 95 | 96 | format(yy)(ad1) 97 | // "01" 98 | format(yy)(Date()) 99 | // "16" 100 | */ 101 | public func yy() -> FormatterOf { 102 | return date(format: "yy") 103 | } 104 | 105 | /** 106 | Year. Zero-padded to 4 characters. 107 | 108 | format(yyyy)(ad1) 109 | // "0001" 110 | format(yyyy)(Date()) 111 | // "2016" 112 | */ 113 | public func yyyy() -> FormatterOf { 114 | return date(format: "yyyy") 115 | } 116 | 117 | /** 118 | Year (in "Week of Year" based calendars). May not always be the same value as calendar year. 119 | */ 120 | public func Y() -> FormatterOf { 121 | return date(format: "Y") 122 | } 123 | 124 | /** 125 | Year (in "Week of Year" based calendars). May not always be the same value as calendar year. Padded and 126 | truncated to 2 characters. 127 | */ 128 | public func YY() -> FormatterOf { 129 | return date(format: "YY") 130 | } 131 | 132 | /** 133 | Year (in "Week of Year" based calendars). May not always be the same value as calendar year. Zero-padded to 134 | 4 characters. 135 | */ 136 | public func YYYY() -> FormatterOf { 137 | return date(format: "YYYY") 138 | } 139 | 140 | // MARK: Quarter 141 | 142 | /** 143 | Quarter. Numerical. 144 | */ 145 | public func Q() -> FormatterOf { 146 | return date(format: "Q") 147 | } 148 | 149 | /** 150 | Quarter. Abbreviation. 151 | */ 152 | public func QQQ() -> FormatterOf { 153 | return date(format: "QQQ") 154 | } 155 | 156 | /** 157 | Quarter. Full name. 158 | */ 159 | public func QQQQ() -> FormatterOf { 160 | return date(format: "QQQQ") 161 | } 162 | 163 | /** 164 | **Stand-alone** quarter. Numerical. 165 | */ 166 | public func q() -> FormatterOf { 167 | return date(format: "q") 168 | } 169 | 170 | /** 171 | **Stand-alone** quarter. Abbreviation. 172 | */ 173 | public func qqq() -> FormatterOf { 174 | return date(format: "qqq") 175 | } 176 | 177 | /** 178 | **Stand-alone** quarter. Full name. 179 | */ 180 | public func qqqq() -> FormatterOf { 181 | return date(format: "qqqq") 182 | } 183 | 184 | // MARK: Month 185 | 186 | /** 187 | Month. Numerical. 188 | */ 189 | public func M() -> FormatterOf { 190 | return date(format: "M") 191 | } 192 | 193 | /** 194 | Month. Numerical. Zero-padded. 195 | */ 196 | public func MM() -> FormatterOf { 197 | return date(format: "MM") 198 | } 199 | 200 | /** 201 | Month. Abbreviation. 202 | */ 203 | public func MMM() -> FormatterOf { 204 | return date(format: "MMM") 205 | } 206 | 207 | /** 208 | Month. Full name. 209 | */ 210 | public func MMMM() -> FormatterOf { 211 | return date(format: "MMMM") 212 | } 213 | 214 | /** 215 | Month. Narrow name. 216 | */ 217 | public func MMMMM() -> FormatterOf { 218 | return date(format: "MMMMM") 219 | } 220 | 221 | // MARK: Week 222 | 223 | /** 224 | Week of year. 225 | */ 226 | public func w() -> FormatterOf { 227 | return date(format: "w") 228 | } 229 | 230 | /** 231 | Week of year. Zero-padded. 232 | */ 233 | public func ww() -> FormatterOf { 234 | return date(format: "ww") 235 | } 236 | 237 | /** 238 | Week of month. 239 | */ 240 | public func W() -> FormatterOf { 241 | return date(format: "W") 242 | } 243 | 244 | // MARK: Day 245 | 246 | /** 247 | Date (day of the month). 248 | */ 249 | public func d() -> FormatterOf { 250 | return date(format: "d") 251 | } 252 | 253 | /** 254 | Date (day of the month). Zero-padded. 255 | */ 256 | public func dd() -> FormatterOf { 257 | return date(format: "dd") 258 | } 259 | 260 | /** 261 | Day of year. 262 | */ 263 | public func D() -> FormatterOf { 264 | return date(format: "D") 265 | } 266 | 267 | /** 268 | Day of year. Zero-padded. 269 | */ 270 | public func DDD() -> FormatterOf { 271 | return date(format: "DDD") 272 | } 273 | 274 | /** 275 | Day of week in month. 276 | */ 277 | public func F() -> FormatterOf { 278 | return date(format: "F") 279 | } 280 | 281 | // MARK: Week day 282 | 283 | /** 284 | Day of week. Short day. 285 | 286 | format(E)(Date()) 287 | // "Tues" 288 | */ 289 | public func E() -> FormatterOf { 290 | return date(format: "E") 291 | } 292 | 293 | /** 294 | Day of week. Full name. 295 | 296 | format(E)(Date()) 297 | // "Tuesday" 298 | */ 299 | public func EEEE() -> FormatterOf { 300 | return date(format: "EEEE") 301 | } 302 | 303 | /** 304 | Day of week. Narrow name. 305 | 306 | format(E)(Date()) 307 | // "T" 308 | */ 309 | public func EEEEE() -> FormatterOf { 310 | return date(format: "EEEEE") 311 | } 312 | 313 | /** 314 | Day of week. Short name. 315 | 316 | format(E)(Date()) 317 | // "Tu" 318 | */ 319 | public func EEEEEE() -> FormatterOf { 320 | return date(format: "EEEEEE") 321 | } 322 | 323 | /** 324 | Day of week. Numeric value. 325 | 326 | format(e)(Date()) 327 | // "1" 328 | */ 329 | public func e() -> FormatterOf { 330 | return date(format: "e") 331 | } 332 | 333 | // MARK: Period 334 | 335 | /** 336 | AM or PM. 337 | 338 | format(a)(Date()) 339 | // "PM" 340 | */ 341 | public func a() -> FormatterOf { 342 | return date(format: "a") 343 | } 344 | 345 | // MARK: Hour 346 | 347 | /** 348 | Hour [1-12]. Use `hh` for zero padding. 349 | */ 350 | public func h() -> FormatterOf { 351 | return date(format: "h") 352 | } 353 | 354 | /** 355 | Hour [0-12]. Zero-padded. 356 | */ 357 | public func hh() -> FormatterOf { 358 | return date(format: "hh") 359 | } 360 | 361 | /** 362 | Hour [0-23]. Use `HH` for zero padding. 363 | */ 364 | public func H() -> FormatterOf { 365 | return date(format: "H") 366 | } 367 | 368 | /** 369 | Hour [0-23]. Zero-padded. 370 | */ 371 | public func HH() -> FormatterOf { 372 | return date(format: "HH") 373 | } 374 | 375 | /** 376 | Hour [0-11]. Use `KK` for zero padding. 377 | */ 378 | public func K() -> FormatterOf { 379 | return date(format: "K") 380 | } 381 | 382 | /** 383 | Hour [0-11]. Zero-padded. 384 | */ 385 | public func KK() -> FormatterOf { 386 | return date(format: "KK") 387 | } 388 | 389 | /** 390 | Hour [1-24]. Use `kk` for zero padding. 391 | */ 392 | public func k() -> FormatterOf { 393 | return date(format: "k") 394 | } 395 | 396 | /** 397 | Hour [1-24]. Zero-padded. 398 | */ 399 | public func kk() -> FormatterOf { 400 | return date(format: "kk") 401 | } 402 | 403 | // MARK: Minute 404 | 405 | /** 406 | Minute. Use `mm` for zero padding. 407 | */ 408 | public func m() -> FormatterOf { 409 | return date(format: "m") 410 | } 411 | 412 | /** 413 | Minute. Zero-padded. 414 | */ 415 | public func mm() -> FormatterOf { 416 | return date(format: "mm") 417 | } 418 | 419 | // MARK: Second 420 | 421 | /** 422 | Second. Use `ss` for zero padding. 423 | */ 424 | public func s() -> FormatterOf { 425 | return date(format: "s") 426 | } 427 | 428 | /** 429 | Second. Zero-padded. 430 | */ 431 | public func ss() -> FormatterOf { 432 | return date(format: "ss") 433 | } 434 | 435 | /** 436 | Fractional second truncated to 1 character. 437 | */ 438 | public func S() -> FormatterOf { 439 | return date(format: "S") 440 | } 441 | 442 | /** 443 | Fractional second truncated to 2 characters. 444 | */ 445 | public func SS() -> FormatterOf { 446 | return date(format: "SS") 447 | } 448 | 449 | /** 450 | Fractional second truncated to 3 characters. 451 | */ 452 | public func SSS() -> FormatterOf { 453 | return date(format: "SSS") 454 | } 455 | 456 | /** 457 | Milliseconds in day. This field behaves _exactly_ like a composite of all time-related fields, not including 458 | the zone fields. As such, it also reflects discontinuities of those fields on DST transition days. On a day 459 | of DST onset, it will jump forward. On a day of DST cessation, it will jump backward. This reflects the fact 460 | that is must be combined with the offset field to obtain a unique local time value. 461 | */ 462 | public func A() -> FormatterOf { 463 | return date(format: "A") 464 | } 465 | 466 | // MARK: Zone 467 | 468 | /** 469 | Time zone. The _short specific non-location format_. Where that is unavailable, falls back to the short 470 | localized GMT format (`O`). 471 | 472 | format(z)(Date()) 473 | // "EDT" 474 | */ 475 | public func z() -> FormatterOf { 476 | return date(format: "z") 477 | } 478 | 479 | /** 480 | The _long specific non-location format_. Where that is unavailable, falls back to the long localized GMT 481 | format (`OOOO`). 482 | 483 | format(zzzz)(Date()) 484 | // "Eastern Daylight Time" 485 | */ 486 | public func zzzz() -> FormatterOf { 487 | return date(format: "zzzz") 488 | } 489 | 490 | /** 491 | The _ISO8601 basic format_ with hours, minutes and optional seconds fields. The format is equivalent to RFC 492 | 822 zone format (when optional seconds field is absent). This is equivalent to the `xxxx` specifier. 493 | 494 | format(Z)(Date()) 495 | // "-0400" 496 | */ 497 | public func Z() -> FormatterOf { 498 | return date(format: "Z") 499 | } 500 | 501 | /** 502 | The _long localized GMT format_. This is equivalent to the `OOOO` specifier. 503 | 504 | format(ZZZZ)(Date()) 505 | // "GMT-04:00" 506 | */ 507 | public func ZZZZ() -> FormatterOf { 508 | return date(format: "ZZZZ") 509 | } 510 | 511 | /** 512 | The _ISO8601 extended format_ with hours, minutes and optional seconds fields. The ISO8601 UTC indicator "Z" 513 | is used when local time offset is 0. This is equivalent to the `XXXXX` specifier. 514 | 515 | format(ZZZZZ)(Date()) 516 | // "-04:00" 517 | */ 518 | public func ZZZZZ() -> FormatterOf { 519 | return date(format: "ZZZZZ") 520 | } 521 | 522 | /** 523 | The _short localized GMT format_. 524 | 525 | format(ZZZZZ)(Date()) 526 | // "GMT-4" 527 | */ 528 | public func O() -> FormatterOf { 529 | return date(format: "O") 530 | } 531 | 532 | /** 533 | The _long localized GMT format_. 534 | 535 | format(ZZZZZ)(Date()) 536 | // "GMT-04:00" 537 | */ 538 | public func OOOO() -> FormatterOf { 539 | return date(format: "OOOO") 540 | } 541 | 542 | /** 543 | The _short generic non-location format_. Where that is unavailable, falls back to the _generic location 544 | format_ (`VVVV`), then the _short localized GMT format_ as the final fallback. 545 | 546 | format(v)(Date()) 547 | // "ET" 548 | */ 549 | public func v() -> FormatterOf { 550 | return date(format: "v") 551 | } 552 | 553 | /** 554 | The _long generic non-location format_. Where that is unavailable, falls back to _generic location format_ 555 | (`VVVV`). 556 | 557 | format(vvvv)(Date()) 558 | // "Eastern Time" 559 | */ 560 | public func vvvv() -> FormatterOf { 561 | return date(format: "vvvv") 562 | } 563 | 564 | /** 565 | The short time zone ID. Where that is unavailable, the special short time zone ID _unk_ (Unknown Zone) is 566 | used. 567 | 568 | format(V)(Date()) 569 | // "usnyc" 570 | 571 | - note: This specifier was originally used for a variant of the short specific non-location format, but it 572 | was deprecated in the later version of this specification. In CLDR 23, the definition of the specifier was 573 | changed to designate a short time zone ID. 574 | */ 575 | public func V() -> FormatterOf { 576 | return date(format: "V") 577 | } 578 | 579 | /** 580 | The long time zone ID. 581 | 582 | format(VV)(Date()) 583 | // "America/New_York" 584 | */ 585 | public func VV() -> FormatterOf { 586 | return date(format: "VV") 587 | } 588 | 589 | /** 590 | The exemplar city (location) for the time zone. Where that is unavailable, the localized exemplar city name 591 | for the special zone _Etc/Unknown_ is used as the fallback (for example, "Unknown City"). 592 | 593 | format(VVV)(Date()) 594 | // "New York" 595 | */ 596 | public func VVV() -> FormatterOf { 597 | return date(format: "VVV") 598 | } 599 | 600 | /** 601 | The _generic location format_. Where that is unavailable, falls back to the _long localized GMT format_ 602 | (`OOOO`). 603 | 604 | This is especially useful when presenting possible timezone choices for user selection, since the naming is 605 | more uniform than the `v` format. 606 | 607 | format(VVVV)(Date()) 608 | // "New York Time" 609 | 610 | - note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830 611 | */ 612 | public func VVVV() -> FormatterOf { 613 | return date(format: "VVVV") 614 | } 615 | 616 | /** 617 | The _ISO8601 basic format_ with hours field and optional minutes field. The ISO8601 UTC indicator "Z" is 618 | used when local time offset is 0. (The same as `x`, plus "Z".) 619 | 620 | format(X)(Date()) 621 | // "-04" 622 | format(X)(utc) 623 | // "Z" 624 | */ 625 | public func X() -> FormatterOf { 626 | return date(format: "X") 627 | } 628 | /** 629 | The _ISO8601 basic format_ with hours and minutes fields. The ISO8601 UTC indicator "Z" is used when local 630 | time offset is 0. (The same as `xx`, plus "Z".) 631 | 632 | format(XX)(Date()) 633 | // "-0400" 634 | format(XX)(utc) 635 | // "Z" 636 | */ 637 | public func XX() -> FormatterOf { 638 | return date(format: "XX") 639 | } 640 | /** 641 | The _ISO8601 basic format_ with hours and minutes fields. The ISO8601 UTC indicator "Z" is used when local 642 | time offset is 0. (The same as `xxx`, plus `Z`.) 643 | 644 | format(XXX)(Date()) 645 | // "-04:00" 646 | format(XXX)(utc) 647 | // "Z" 648 | */ 649 | public func XXX() -> FormatterOf { 650 | return date(format: "XXX") 651 | } 652 | /** 653 | The _ISO8601 basic format_ with hours, minutes and optional seconds fields. The ISO8601 UTC indicator "Z" is 654 | used when local time offset is 0. (The same as `xxxx`, plus "Z".) 655 | 656 | format(XXXX)(Date()) 657 | // "-0400" 658 | format(XXXX)(utc) 659 | // "Z" 660 | 661 | - note: The seconds field is not supported by the ISO8601 specification. 662 | */ 663 | public func XXXX() -> FormatterOf { 664 | return date(format: "XXXX") 665 | } 666 | /** 667 | The _ISO8601 extended format_ with hours, minutes and optional seconds fields. The ISO8601 UTC indicator "Z" 668 | is used when local time offset is 0. (The same as `xxxxx`, plus "Z".) 669 | 670 | format(XXXXX)(Date()) 671 | // "-04:00" 672 | format(XXXXX)(utc) 673 | // "Z" 674 | 675 | - note: The seconds field is not supported by the ISO8601 specification. 676 | */ 677 | public func XXXXX() -> FormatterOf { 678 | return date(format: "XXXXX") 679 | } 680 | 681 | /** 682 | The _ISO8601 basic format_ with hours field and optional minutes field. (The same as `x`, plus "Z".) 683 | 684 | format(x)(Date()) 685 | // "-04" 686 | */ 687 | public func x() -> FormatterOf { 688 | return date(format: "x") 689 | } 690 | /** 691 | The _ISO8601 basic format_ with hours and minutes fields. (The same as `x`, plus "Z".) 692 | 693 | format(xx)(Date()) 694 | // "-0400" 695 | */ 696 | public func xx() -> FormatterOf { 697 | return date(format: "xx") 698 | } 699 | /** 700 | The _ISO8601 basic format_ with hours and minutes fields. (The same as `xxx`, plus `Z`.) 701 | 702 | format(xxx)(Date()) 703 | // "-04:00" 704 | */ 705 | public func xxx() -> FormatterOf { 706 | return date(format: "xxx") 707 | } 708 | /** 709 | The _ISO8601 basic format_ with hours, minutes and optional seconds fields. (The same as `xxxx`, plus "Z".) 710 | 711 | format(xxxx)(Date()) 712 | // "-0400" 713 | 714 | - note: The seconds field is not supported by the ISO8601 specification. 715 | */ 716 | public func xxxx() -> FormatterOf { 717 | return date(format: "xxxx") 718 | } 719 | /** 720 | The _ISO8601 extended format_ with hours, minutes and optional seconds fields. (The same as `xxxxx`, plus 721 | "Z".) 722 | 723 | format(xxxxx)(Date()) 724 | // "-04:00" 725 | 726 | - note: The seconds field is not supported by the ISO8601 specification. 727 | */ 728 | public func xxxxx() -> FormatterOf { 729 | return date(format: "xxxxx") 730 | } 731 | -------------------------------------------------------------------------------- /Sources/Formatter.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A formatter. The `Result` type means the returned value at the end. The more formatters you compose, the 3 | more this will build up arguments. _E.g._, from `Result` to `(Int) -> Result` to 4 | `(Character) -> (Int) -> Result`, etc. 5 | */ 6 | 7 | public struct Formatter { 8 | public let format: (@escaping (String) -> Result) -> A 9 | } 10 | 11 | /** 12 | A formatter that transforms `Input` into `Result`. 13 | */ 14 | public typealias FormatterOf = Formatter Result> 15 | 16 | /** 17 | Run the formatter to produce a `String` value. 18 | */ 19 | public func format(_ formatter: Formatter) -> A { 20 | return formatter.format { string in string } 21 | } 22 | 23 | // MARK: 24 | 25 | /** 26 | Composition operator. Combines two formatters. 27 | 28 | - parameters: 29 | - lhs: a formatter 30 | - rhs: a formatter 31 | - returns: A formatter combining the left-hand side with the right-hand side. 32 | */ 33 | public func % (lhs: Formatter, rhs: Formatter) -> Formatter { 34 | return Formatter { stringToResult in 35 | lhs.format { leftString in 36 | rhs.format { rightString in 37 | stringToResult(leftString + rightString) 38 | } 39 | } 40 | } 41 | } 42 | 43 | private func s(_ string: String) -> Formatter { 44 | return Formatter { stringToResult in stringToResult(string) } 45 | } 46 | 47 | /** 48 | Composition operator. Combines a formatter and a string. 49 | 50 | format(int % " luftballons")(99) 51 | // "99 luftballons" 52 | 53 | - parameters: 54 | - lhs: a formatter 55 | - rhs: a string 56 | - returns: A formatter appending the given formatter with the given string. 57 | */ 58 | public func % (lhs: Formatter, rhs: String) -> Formatter { 59 | return lhs % s(rhs) 60 | } 61 | 62 | /** 63 | Composition operator. Combines a string and a formatter. 64 | 65 | format("hello " % string)("world") 66 | // "hello world" 67 | 68 | - parameters: 69 | - lhs: a string 70 | - rhs: a formatter 71 | - returns: A formatter with the given string prepended to the given formatter. 72 | */ 73 | public func % (lhs: String, rhs: Formatter) -> Formatter { 74 | return s(lhs) % rhs 75 | } 76 | 77 | // MARK: 78 | 79 | /** 80 | Monoidal composition. Will append previous formatter input to the right-hand formatter. 81 | 82 | format("It's day " % D % " of " <> y % ".")(Date()) 83 | // "It's day 180 of 2016." 84 | 85 | - parameters: 86 | - lhs: a formatter 87 | - rhs: a formatter of the same type 88 | - returns: A combined formatter matching the types of the given formatters. 89 | */ 90 | public func <> (lhs: FormatterOf, rhs: FormatterOf) -> FormatterOf { 91 | return Formatter { stringToResult in 92 | { input in 93 | lhs.format { leftString in 94 | rhs.format { rightString in 95 | stringToResult(leftString + rightString) 96 | }(input) 97 | }(input) 98 | } 99 | } 100 | } 101 | 102 | // MARK: 103 | 104 | /** 105 | Function compose two formatters. Will feed the result of one formatter into another. 106 | 107 | format(left(2, "0") .% hex)(10) 108 | // "0a" 109 | 110 | - parameters: 111 | - lhs: a formatter that takes a string 112 | - rhs: a formatter 113 | */ 114 | public func .% (lhs: Formatter B>, rhs: Formatter) -> Formatter { 115 | return Formatter { stringToResult in rhs.format(lhs.format(stringToResult)) } 116 | } 117 | 118 | // MARK: 119 | // paren-reducing overloads: these can go away when generic accessors (_e.g._, `var string`) are supported 120 | 121 | public func format(_ formatter: () -> Formatter) -> A { 122 | return formatter().format { $0 } 123 | } 124 | 125 | public func % (lhs: () -> Formatter, rhs: String) -> Formatter { 126 | return lhs() % s(rhs) 127 | } 128 | 129 | public func % (lhs: String, rhs: () -> Formatter) -> Formatter { 130 | return s(lhs) % rhs() 131 | } 132 | 133 | public func % (lhs: Formatter, rhs: () -> Formatter) -> Formatter { 134 | return lhs % rhs() 135 | } 136 | 137 | public func % (lhs: () -> Formatter, rhs: Formatter) -> Formatter { 138 | return lhs() % rhs 139 | } 140 | 141 | public func <> (lhs: () -> FormatterOf, rhs: FormatterOf) -> FormatterOf { 142 | return lhs() <> rhs 143 | } 144 | 145 | public func <> (lhs: FormatterOf, rhs: () -> FormatterOf) -> FormatterOf { 146 | return lhs <> rhs() 147 | } 148 | 149 | public func <> (lhs: () -> FormatterOf, rhs: () -> FormatterOf) -> FormatterOf { 150 | return lhs() <> rhs() 151 | } 152 | 153 | public func .% (lhs: () -> Formatter B>, rhs: Formatter) -> Formatter { 154 | return lhs() .% rhs 155 | } 156 | 157 | public func .% (lhs: Formatter B>, rhs: () -> Formatter) -> Formatter { 158 | return lhs .% rhs() 159 | } 160 | 161 | public func .% (lhs: () -> Formatter B>, rhs: () -> Formatter) 162 | -> Formatter { 163 | 164 | return lhs() .% rhs() 165 | } 166 | -------------------------------------------------------------------------------- /Sources/Formatters.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: String types 4 | 5 | /** 6 | Render a string. 7 | */ 8 | public func string() -> FormatterOf { 9 | return Formatter { f in { string in f(string) } } 10 | } 11 | 12 | /** 13 | Render the `description` of a `CustomStringConvertible`. 14 | */ 15 | public func describe() -> FormatterOf { 16 | return Formatter { f in { convertible in f(convertible.description) } } 17 | } 18 | 19 | /** 20 | Render the `debugDescription` of a `CustomDebugStringConvertible`. 21 | */ 22 | public func debug() -> FormatterOf { 23 | return Formatter { f in { convertible in f(convertible.debugDescription) } } 24 | } 25 | 26 | /** 27 | Render a character. 28 | */ 29 | public func char() -> FormatterOf { 30 | return Formatter { f in { char in f(String(char)) } } 31 | } 32 | 33 | /** 34 | Render a character code. 35 | 36 | format("Got: '" % char % "' (" <> asInt % ")")("a") 37 | // "Got: 'a' (92)" 38 | */ 39 | public func asInt() -> FormatterOf { 40 | return Formatter { f in { char in f(String(String(char).utf8.first!)) } } 41 | } 42 | 43 | /** 44 | Render uppercased. 45 | */ 46 | public func upper() -> FormatterOf { 47 | return Formatter { f in { string in f(string.uppercased()) } } 48 | } 49 | 50 | /** 51 | Render lowercased. 52 | */ 53 | public func lower() -> FormatterOf { 54 | return Formatter { f in { string in f(string.lowercased()) } } 55 | } 56 | 57 | // MARK: Numbers 58 | 59 | /** 60 | Render an integer. 61 | */ 62 | public func int() -> FormatterOf { 63 | return Formatter { f in { int in f(String(int)) } } 64 | } 65 | 66 | /** 67 | Render a double. 68 | */ 69 | public func float() -> FormatterOf { 70 | return Formatter { f in { double in f(String(double)) } } 71 | } 72 | 73 | public func number(_ formatter: NumberFormatter) -> FormatterOf { 74 | return Formatter { f in { n in f(formatter.string(from: n as NSNumber)!) } } 75 | } 76 | 77 | public func number(_ formatter: NumberFormatter) -> FormatterOf { 78 | return Formatter { f in { n in f(formatter.string(from: n as NSNumber)!) } } 79 | } 80 | 81 | private func number(_ builder: (NumberFormatter) -> ()) -> FormatterOf { 82 | let nf = NumberFormatter() 83 | builder(nf) 84 | return number(nf) 85 | } 86 | 87 | private func number(_ builder: (NumberFormatter) -> ()) -> FormatterOf { 88 | let nf = NumberFormatter() 89 | builder(nf) 90 | return number(nf) 91 | } 92 | 93 | public func sci() -> FormatterOf { 94 | return number { $0.numberStyle = .scientific } 95 | } 96 | 97 | /** 98 | Add commas (or the current locale's grouping separator) to an integral. 99 | 100 | format(12_000) 101 | // "12,000" 102 | */ 103 | public func delimit() -> FormatterOf { 104 | return number { ($0.numberStyle, $0.usesGroupingSeparator) = (.decimal, true) } 105 | } 106 | 107 | /** 108 | Add a suffix to an integral. 109 | 110 | format(ord)(1) 111 | // "1st" 112 | */ 113 | @available(/*iOS 9, */OSX 10.11, tvOS 9, watchOS 2, *) 114 | public func ord() -> FormatterOf { 115 | return number { $0.numberStyle = .ordinal } 116 | } 117 | 118 | // MARK: Padding 119 | 120 | /** 121 | Pad the left-hand side of a string till it reaches `length` characters wide, if necessary filling with 122 | character `pad`. 123 | 124 | format(left(2, "0") .% hex)(10) 125 | // "0a" 126 | 127 | - parameters: 128 | - length: length rendered string is padded to 129 | - pad: character used for padding (defaults to a space) 130 | */ 131 | public func left(_ length: Int, _ pad: Character = " ") -> FormatterOf { 132 | return Formatter { f in 133 | { string in 134 | let pad = repeatElement(String(pad), count: max(0, length - string.characters.count)) 135 | .joined(separator: "") 136 | return f(pad + string) 137 | } 138 | } 139 | } 140 | 141 | /** 142 | Pad the right-hand side of a string till it reaches `length` characters wide, if necessary filling with 143 | character `pad`. 144 | 145 | format(right(3) .% int % " | " string)(1)("import Formatting") 146 | // "1 | import Formatting" 147 | 148 | - parameters: 149 | - length: length rendered string is padded to 150 | - pad: character used for padding (defaults to a space) 151 | */ 152 | public func right(_ length: Int, _ pad: Character = " ") -> FormatterOf { 153 | return Formatter { f in 154 | { string in 155 | let pad = repeatElement(String(pad), count: max(0, length - string.characters.count)) 156 | .joined(separator: "") 157 | return f(string + pad) 158 | } 159 | } 160 | } 161 | 162 | /** 163 | Pad the right- and left-hand side of a string till it reaches `length` characters wide, if necessary 164 | filling with character `pad`. 165 | 166 | format(left(2, "0") .% hex)(10) 167 | // "0a" 168 | 169 | - parameters: 170 | - length: length rendered string is padded to 171 | - pad: character used for padding (defaults to a space) 172 | 173 | - note: `center` is right-hand biased and will pad the right-hand side with an extra character when an odd 174 | number of characters are necessary for padding. 175 | */ 176 | public func center(_ length: Int, _ pad: Character = " ") -> FormatterOf { 177 | return Formatter { f in 178 | { string in 179 | let half = Double(max(0, length - string.characters.count)) / 2 180 | let p = String(pad) 181 | let l = repeatElement(p, count: Int(floor(half))).joined(separator: "") 182 | let r = repeatElement(p, count: Int(ceil(half))).joined(separator: "") 183 | return f(l + string + r) 184 | } 185 | } 186 | } 187 | 188 | /** 189 | Fit in the given length, truncating on the left. 190 | */ 191 | public func fitLeft(_ length: Int) -> FormatterOf { 192 | return Formatter { f in 193 | { string in f(String(string.characters.prefix(length))) } 194 | } 195 | } 196 | 197 | /** 198 | Fit in the given length, truncating on the right. 199 | */ 200 | public func fitRight(_ length: Int) -> FormatterOf { 201 | return Formatter { f in 202 | { string in f(String(string.characters.suffix(length))) } 203 | } 204 | } 205 | 206 | // MARK: Bases 207 | 208 | /** 209 | Render an integral with the given base. 210 | */ 211 | public func base(_ radix: Int) -> FormatterOf { 212 | return Formatter { f in { int in f(String(int, radix: radix)) } } 213 | } 214 | 215 | /** 216 | Render an integer using binary notation. 217 | */ 218 | public func bin() -> FormatterOf { 219 | return base(2) 220 | } 221 | 222 | /** 223 | Render an integer using octal notation. 224 | */ 225 | public func oct() -> FormatterOf { 226 | return base(8) 227 | } 228 | 229 | /** 230 | Render an integer using hexidecimal notation 231 | */ 232 | public func hex() -> FormatterOf { 233 | return base(16) 234 | } 235 | 236 | /** 237 | Render an integer using binary notation with a leading `0b`. 238 | */ 239 | public func prefixBin() -> FormatterOf { 240 | return "0b" % bin 241 | } 242 | 243 | /** 244 | Render an integer using octal notation with a leading `0o`. 245 | */ 246 | public func prefixOct() -> FormatterOf { 247 | return "0o" % oct 248 | } 249 | 250 | /** 251 | Render an integer using hexidecimal notation with a leading `0x`. 252 | */ 253 | public func prefixHex() -> FormatterOf { 254 | return "0x" % hex 255 | } 256 | 257 | /** 258 | Renders a given byte count using a given `Foundation.ByteCountFormatter`. 259 | */ 260 | public func bytes(_ formatter: ByteCountFormatter) -> FormatterOf { 261 | return Formatter { f in { byteCount in f(formatter.string(fromByteCount: Int64(byteCount))) } } 262 | } 263 | 264 | /** 265 | Renders a given byte count using a `Foundation.ByteCountFormatter`. 266 | */ 267 | public func bytes() -> FormatterOf { 268 | return bytes(ByteCountFormatter()) 269 | } 270 | -------------------------------------------------------------------------------- /Sources/Operators.swift: -------------------------------------------------------------------------------- 1 | infix operator <> : AdditionPrecedence 2 | infix operator .% : MultiplicationPrecedence 3 | -------------------------------------------------------------------------------- /Sources/Uncurry.swift: -------------------------------------------------------------------------------- 1 | public func format(_ formatter: Formatter String>, _ a: A) -> String { 2 | return format(formatter)(a) 3 | } 4 | 5 | public func format(_ formatter: Formatter (B) -> String>, _ a: A, _ b: B) -> String { 6 | return format(formatter)(a)(b) 7 | } 8 | 9 | public func format(_ formatter: Formatter (B) -> (C) -> String>, _ a: A, _ b: B, _ c: C) -> String { 10 | return format(formatter)(a)(b)(c) 11 | } 12 | 13 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> String>, _ a: A, _ b: B, _ c: C, _ d: D) -> String { 14 | return format(formatter)(a)(b)(c)(d) 15 | } 16 | 17 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> String { 18 | return format(formatter)(a)(b)(c)(d)(e) 19 | } 20 | 21 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> String { 22 | return format(formatter)(a)(b)(c)(d)(e)(f) 23 | } 24 | 25 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G) -> String { 26 | return format(formatter)(a)(b)(c)(d)(e)(f)(g) 27 | } 28 | 29 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H) -> String { 30 | return format(formatter)(a)(b)(c)(d)(e)(f)(g)(h) 31 | } 32 | 33 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> (I) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I) -> String { 34 | return format(formatter)(a)(b)(c)(d)(e)(f)(g)(h)(i) 35 | } 36 | 37 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> (I) -> (J) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J) -> String { 38 | return format(formatter)(a)(b)(c)(d)(e)(f)(g)(h)(i)(j) 39 | } 40 | 41 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> (I) -> (J) -> (K) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J, _ k: K) -> String { 42 | return format(formatter)(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k) 43 | } 44 | 45 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> (I) -> (J) -> (K) -> (L) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J, _ k: K, _ l: L) -> String { 46 | return format(formatter)(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l) 47 | } 48 | 49 | public func format(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> (I) -> (J) -> (K) -> (L) -> (M) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J, _ k: K, _ l: L, _ m: M) -> String { 50 | return format(formatter)(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)(m) 51 | } 52 | 53 | public func print(_ formatter: Formatter String>, _ a: A) { 54 | print(format(formatter, a)) 55 | } 56 | 57 | public func print(_ formatter: Formatter (B) -> String>, _ a: A, _ b: B) { 58 | print(format(formatter, a, b)) 59 | } 60 | 61 | public func print(_ formatter: Formatter (B) -> (C) -> String>, _ a: A, _ b: B, _ c: C) { 62 | print(format(formatter, a, b, c)) 63 | } 64 | 65 | public func print(_ formatter: Formatter (B) -> (C) -> (D) -> String>, _ a: A, _ b: B, _ c: C, _ d: D) { 66 | print(format(formatter, a, b, c, d)) 67 | } 68 | 69 | public func print(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E) { 70 | print(format(formatter, a, b, c, d, e)) 71 | } 72 | 73 | public func print(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) { 74 | print(format(formatter, a, b, c, d, e, f)) 75 | } 76 | 77 | public func print(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G) { 78 | print(format(formatter, a, b, c, d, e, f, g)) 79 | } 80 | 81 | public func print(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H) { 82 | print(format(formatter, a, b, c, d, e, f, g, h)) 83 | } 84 | 85 | public func print(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> (I) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I) { 86 | print(format(formatter, a, b, c, d, e, f, g, h, i)) 87 | } 88 | 89 | public func print(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> (I) -> (J) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J) { 90 | print(format(formatter, a, b, c, d, e, f, g, h, i, j)) 91 | } 92 | 93 | public func print(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> (I) -> (J) -> (L) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J, _ l: L) { 94 | print(format(formatter, a, b, c, d, e, f, g, h, i, j, l)) 95 | } 96 | 97 | public func print(_ formatter: Formatter (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> (I) -> (J) -> (L) -> (M) -> String>, _ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J, _ l: L, _ m: M) { 98 | print(format(formatter, a, b, c, d, e, f, g, h, i, j, l, m)) 99 | } 100 | 101 | // MARK: 102 | 103 | public func format(_ formatter: () -> Formatter String>, _ a: A) -> String { 104 | return format(formatter)(a) 105 | } 106 | 107 | public func print(_ formatter: () -> Formatter String>, _ a: A) { 108 | print(format(formatter, a)) 109 | } 110 | -------------------------------------------------------------------------------- /Tests/Formatting/FormattingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Formatting 3 | 4 | class FormattingTests: XCTestCase { 5 | func testComposition() { 6 | XCTAssertEqual("Got: 'a' (97)", 7 | format("Got: '" % char % "' (" <> asInt % ")", "a")) 8 | 9 | XCTAssertEqual("2.3e6", 10 | format(lower .% sci, 2_300_000)) 11 | } 12 | 13 | func testString() { 14 | XCTAssertEqual("Hello, world!", 15 | format("Hello, " % string % "!", "world")) 16 | } 17 | 18 | func testDescribe() { 19 | struct Foo: CustomStringConvertible { 20 | var description: String { return "foo" } 21 | } 22 | XCTAssertEqual("x = foo", 23 | format("x = " % describe, Foo())) 24 | } 25 | 26 | func testDebug() { 27 | struct Foo: CustomDebugStringConvertible { 28 | var debugDescription: String { return "" } 29 | } 30 | XCTAssertEqual("x = ", 31 | format("x = " % debug, Foo())) 32 | } 33 | 34 | func testChar() { 35 | XCTAssertEqual("Got: 'a'", 36 | format("Got: '" % char % "'", "a" as Character)) 37 | } 38 | 39 | func testAsInt() { 40 | XCTAssertEqual("Got: 97", 41 | format("Got: " % asInt, "a" as Character)) 42 | } 43 | 44 | func testUpper() { 45 | XCTAssertEqual("HELLO, WORLD!", 46 | format("HELLO, " % upper % "!", "world")) 47 | } 48 | 49 | func testLower() { 50 | XCTAssertEqual("dr. jones", 51 | format("dr. " % lower, "Jones")) 52 | } 53 | 54 | func testInt() { 55 | XCTAssertEqual("100%", 56 | format(int % "%", 100)) 57 | } 58 | 59 | func testFloat() { 60 | XCTAssertEqual("98.6", 61 | format(float, 98.6)) 62 | } 63 | 64 | func testSci() { 65 | XCTAssertEqual("2.3e6", 66 | format(lower .% sci, 2_300_000)) 67 | } 68 | 69 | func testDelimit() { 70 | XCTAssertEqual("1,000,000", 71 | format(delimit, 1_000_000)) 72 | } 73 | 74 | func testOrd() { 75 | if #available(/*iOS 9, */OSX 10.11, tvOS 9, watchOS 2, *) { 76 | XCTAssertEqual("10th", 77 | format(ord, 10)) 78 | } 79 | } 80 | 81 | func testLeft() { 82 | XCTAssertEqual("0a", 83 | format(left(2, "0") .% hex, 10)) 84 | } 85 | 86 | func testRight() { 87 | XCTAssertEqual("1. Introduction", 88 | format(right(3) % " " % string, "1.", "Introduction")) 89 | } 90 | 91 | func testCenter() { 92 | XCTAssertEqual(" Hello ", 93 | format(center(20), "Hello")) 94 | } 95 | 96 | func testFitLeft() { 97 | XCTAssertEqual("Abra cadab", 98 | format(fitLeft(10), "Abra cadabra")) 99 | } 100 | 101 | func testFitRight() { 102 | XCTAssertEqual("ra cadabra", 103 | format(fitRight(10), "Abra cadabra")) 104 | } 105 | 106 | func testBase() { 107 | XCTAssertEqual("10", 108 | format(base(6), 6)) 109 | } 110 | 111 | func testBin() { 112 | XCTAssertEqual("10", 113 | format(bin, 2)) 114 | } 115 | 116 | func testOct() { 117 | XCTAssertEqual("10", 118 | format(oct, 8)) 119 | } 120 | 121 | func testHex() { 122 | XCTAssertEqual("10", 123 | format(hex, 16)) 124 | } 125 | 126 | func testPrefixBin() { 127 | XCTAssertEqual("0b10", 128 | format(prefixBin, 2)) 129 | } 130 | 131 | func testPrefixOct() { 132 | XCTAssertEqual("0o10", 133 | format(prefixOct, 8)) 134 | } 135 | 136 | func testPrefixHex() { 137 | XCTAssertEqual("0x10", 138 | format(prefixHex, 16)) 139 | } 140 | 141 | func testBytes() { 142 | XCTAssertEqual("1 KB", 143 | format(bytes, 1024)) 144 | } 145 | 146 | static var allTests : [(String, (FormattingTests) -> () throws -> Void)] { 147 | return [ 148 | ("testComposition", testComposition), 149 | ("testString", testString), 150 | ("testDescribe", testDescribe), 151 | ("testDebug", testDebug), 152 | ("testChar", testChar), 153 | ("testAsInt", testAsInt), 154 | ("testUpper", testUpper), 155 | ("testLower", testLower), 156 | ("testInt", testInt), 157 | ("testFloat", testFloat), 158 | ("testSci", testSci), 159 | ("testDelimit", testDelimit), 160 | ("testOrd", testOrd), 161 | ("testLeft", testLeft), 162 | ("testRight", testRight), 163 | ("testCenter", testCenter), 164 | ("testFitLeft", testFitLeft), 165 | ("testFitRight", testFitRight), 166 | ("testBase", testBase), 167 | ("testBin", testBin), 168 | ("testOct", testOct), 169 | ("testHex", testHex), 170 | ("testPrefixBin", testPrefixBin), 171 | ("testPrefixOct", testPrefixOct), 172 | ("testPrefixHex", testPrefixHex), 173 | ("testBytes", testBytes), 174 | ] 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import FormattingTestSuite 3 | 4 | XCTMain([ 5 | testCase(FormattingTests.allTests), 6 | ]) 7 | --------------------------------------------------------------------------------