-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7f4f5f2
Showing
21 changed files
with
2,915 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# OS X | ||
.DS_Store | ||
|
||
# Xcode | ||
build/ | ||
*.pbxuser | ||
!default.pbxuser | ||
*.mode1v3 | ||
!default.mode1v3 | ||
*.mode2v3 | ||
!default.mode2v3 | ||
*.perspectivev3 | ||
!default.perspectivev3 | ||
xcuserdata | ||
*.xccheckout | ||
*.moved-aside | ||
DerivedData | ||
*.hmap | ||
*.ipa | ||
*.dot | ||
html |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
coverage_service: coveralls | ||
xcodeproj: Sprinter.xcodeproj | ||
scheme: "Sprinter (Mac)" | ||
source_directory: Sources/* | ||
ignore: | ||
- Tests/* |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
4.0 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
language: swift | ||
osx_image: xcode9 | ||
|
||
env: | ||
- LC_CTYPE=en_US.UTF-8 LANG=en_US.UTF-8 | ||
before_install: | ||
- rvm install ruby-2.4.2 | ||
install: | ||
- bundle install --without=documentation | ||
script: | ||
- set -o pipefail | ||
- xcodebuild -project Sprinter.xcodeproj -scheme "Sprinter (Mac)" -sdk macosx clean build test | bundle exec xcpretty | ||
after_success: bundle exec slather |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Change Log | ||
|
||
## [0.1](https://github.com/nicklockwood/Sprinter/releases/tag/0.1) (2017-11-22) | ||
|
||
- First release |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
source 'https://rubygems.org' | ||
|
||
gem 'slather' | ||
gem 'xcpretty' |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
GEM | ||
remote: https://rubygems.org/ | ||
specs: | ||
CFPropertyList (2.3.5) | ||
activesupport (4.2.10) | ||
i18n (~> 0.7) | ||
minitest (~> 5.1) | ||
thread_safe (~> 0.3, >= 0.3.4) | ||
tzinfo (~> 1.1) | ||
claide (1.0.2) | ||
clamp (0.6.5) | ||
colored2 (3.1.2) | ||
concurrent-ruby (1.0.5) | ||
i18n (0.9.0) | ||
concurrent-ruby (~> 1.0) | ||
mini_portile2 (2.1.0) | ||
minitest (5.10.3) | ||
nanaimo (0.2.3) | ||
nokogiri (1.6.8.1) | ||
mini_portile2 (~> 2.1.0) | ||
rouge (2.0.7) | ||
slather (2.4.3) | ||
CFPropertyList (~> 2.2) | ||
activesupport (>= 4.0.2, < 5) | ||
clamp (~> 0.6) | ||
nokogiri (>= 1.6, < 1.7) | ||
xcodeproj (~> 1.4) | ||
thread_safe (0.3.6) | ||
tzinfo (1.2.3) | ||
thread_safe (~> 0.1) | ||
xcodeproj (1.5.3) | ||
CFPropertyList (~> 2.3.3) | ||
claide (>= 1.0.2, < 2.0) | ||
colored2 (~> 3.1) | ||
nanaimo (~> 0.2.3) | ||
xcpretty (0.2.8) | ||
rouge (~> 2.0.7) | ||
|
||
PLATFORMS | ||
ruby | ||
|
||
DEPENDENCIES | ||
slather | ||
xcpretty | ||
|
||
BUNDLED WITH | ||
1.15.4 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2017 Nick Lockwood | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// swift-tools-version:4.0 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Sprinter" | ||
) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
[![Travis](https://img.shields.io/travis/nicklockwood/Sprinter.svg)](https://travis-ci.org/nicklockwood/Sprinter) | ||
[![Coveralls](https://coveralls.io/repos/github/nicklockwood/Sprinter/badge.svg)](https://coveralls.io/github/nicklockwood/Sprinter) | ||
[![Swift 3.2](https://img.shields.io/badge/swift-3.2-orange.svg?style=flat)](https://developer.apple.com/swift) | ||
[![Swift 4.0](https://img.shields.io/badge/swift-4.0-red.svg?style=flat)](https://developer.apple.com/swift) | ||
[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://opensource.org/licenses/MIT) | ||
[![Twitter](https://img.shields.io/badge/twitter-@nicklockwood-blue.svg)](http://twitter.com/nicklockwood) | ||
|
||
# Sprinter | ||
|
||
- [Introduction](#introduction) | ||
- [What?](#what) | ||
- [Why?](#why) | ||
- [How?](#how) | ||
- [Usage](#usage) | ||
- [Installation](#installation) | ||
- [Integration](#integration) | ||
- [Localization](#localization) | ||
- [Thread Safety](#thread-safety) | ||
- [Advanced Usage](#advanced-usage) | ||
|
||
|
||
# Introduction | ||
|
||
## What? | ||
|
||
Sprinter is a library for Mac and iOS for formatting strings at runtime using the printf / NSLog format token conventions. | ||
|
||
The aim is to provide a type-safe, Swift-friendly interface for string formatting that is fully compatible with the printf specification, as well as Apple's proprietary extensions for working with Objective-C data types. | ||
|
||
The name "Sprinter" is derived from "String-Printer", just like the `sprintf` function in the C standard library. | ||
|
||
|
||
## Why? | ||
|
||
Although Swift already offers string formatting support in the form of the `String(format:arguments:)` initializer, Swift's support is a fairly crude wrapper around the Objective-C API, and lacks support for some of the standard printf formatting features and data types. For example, there is no way to use the following format string in Swift: | ||
|
||
"Hello %s, how are you?" | ||
|
||
Because the `%s` token expects a C string (a pointer to a zero-terminated array of `CChar`), which the Swift `String(format:arguments:)` method won't accept. Instead, you must use the platform-specific `%@` token instead, which limits reusability of strings between platforms. | ||
|
||
Swift also provides no way to validate or inspect format strings. If the format contains a typo, or the format arguments don't match the ones in your code, the string will be displayed incorrectly at runtime, or worse, may crash or cause silent memory corruption. | ||
|
||
Sprinter solves these issues by exposing the argument types for each format string, so you can write runtime validation logic and handle errors gracefully. | ||
|
||
The Sprinter library could also be used as the basis for unit tests that validate your strings at build time, or even as part of a code generation pipeline to provide strongly-typed string properties and methods. | ||
|
||
|
||
## How? | ||
|
||
Sprinter implements a robust string format parser based on the original [IEEE printf spec](http://pubs.opengroup.org/onlinepubs/009695399/functions/printf.html) along with [Apple's additions](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html) for Objective-C. It makes use of Swift's string formatter internally, but performs pre-validation and type conversion of arguments to ensure that invalid types are never passed to the underlying implementation. | ||
|
||
Sprinter includes a comprehensive test suite to ensure spec compliance, and output compatibility with Apple's formatter. | ||
|
||
|
||
# Usage | ||
|
||
## Installation | ||
|
||
The entire Sprinter API is encapsulated in a single file, and everything public is prefixed or namespaced, so you can simply drag the `Sprinter.swift` file into your project to use it. If you prefer, there's a framework for Mac and iOS that you can import, or you can use CocoaPods, Carthage, or Swift Package Manager on Linux. | ||
|
||
To install Sprinter using CocoaPods, add the following to your Podfile: | ||
|
||
pod 'Sprinter', '~> 0.1.0' | ||
|
||
Sprinter works with Swift 3.2 and 4.x | ||
|
||
|
||
## Integration | ||
|
||
To format a string using Sprinter, you first create a `FormatString` instance, as follows: | ||
|
||
```swift | ||
let formatString = try FormatString("I have %i apples and %i bananas") | ||
``` | ||
|
||
Note the `try` keyword - the `FormatString` initializer performs validation of the string, and will throw an error if the format is invalid. Once you have constructed the formatString object, you can use the `print()` method to output the formatted string. The `print()` method is variadic, which is convenient for passing arguments. There is also a second form that accepts a single array of arguments. | ||
|
||
You would use the `print()` method as follows: | ||
|
||
```swift | ||
let string = try formatString.print(5, 6) | ||
print(string) // I have 5 apples and 6 bananas | ||
``` | ||
|
||
You'll notice that the `print()` function also requires `try`. This method will throw an error if the arguments you pass do not match the placeholders in the original format string. Errors thrown by either the `FormatString` initializer or the `print()` method will all be of type `FormatString.Error`, for example: | ||
|
||
```swift | ||
let formatString = try FormatString("I have %y apples") // throws FormatString.error.unexpectedToken("y") | ||
|
||
let string = try FormatString("I have %i apples").print("foo") // throws FormatString.error.argumentMismatch(1, String.self, Int.self) | ||
``` | ||
|
||
You can determine the required argument types before calling the `print()` method by using the `types` property of the `FormatString`, which returns an array of Swift Type values: | ||
|
||
```swift | ||
let types = formatString.types | ||
print(types) // Int, Int | ||
``` | ||
|
||
This is typically not useful at runtime (incorrect arguments would be a programming error that should be fixed before release), but it could be used in an automated test to verify that a given localized string key has the same argument types in each language. | ||
|
||
|
||
## Localization | ||
|
||
The `FormatString` constructor also takes an optional `locale` argument, which can be used to localize the output: | ||
|
||
```swift | ||
let french = try FormatString("I have %i apples", locale: Locale(identifier: "fr-FR")) | ||
``` | ||
|
||
This will affect how locale-specific formatting and punctuation is displayed, for example: | ||
|
||
```swift | ||
let english = try FormatString("%'g", locale: Locale(identifier: "en-US") | ||
try print(english.print(1234.56)) // 1,234.56 | ||
|
||
let french = try FormatString("%'g", locale: Locale(identifier: "fr-FR") | ||
try print(french.print(1234.56)) // 1 234,56 | ||
|
||
let german = try FormatString("%'g", locale: Locale(identifier: "de-DE") | ||
try print(german.print(1234.56)) // 1.234,56 | ||
``` | ||
|
||
## Thread Safety | ||
|
||
It is safe to create `FormatString` instances on a background thread. | ||
|
||
Once created, a given `FormatString` instance is stateless, so the same instance can safely be used to print strings on multiple threads concurrently. | ||
|
||
|
||
## Advanced Usage | ||
|
||
It may seem cumbersome to have to create a `StringFormat` object before printing, but it serves two purposes: | ||
|
||
1. It allows validation and type inspection of the string before the point of use. This means you can be confident that there will be no surprise errors when it is called. | ||
|
||
2. The expensive string parsing and `NumberFormatter` initialization steps can be performed once and then stored, not repeated each time the string is displayed. | ||
|
||
For these reasons, it's recommended that you store and re-use your `FormatString` objects. You can either do this up-front for all strings, or lazily the first time each string is displayed - whichever makes more sense for your app. | ||
|
||
A good approach would be to create a wrapper function that encapsulates your app-specific string requirements. For example, you might want to ignore string format errors in production (since it's too late to fix by that point), and just display a blank string instead. Here is an example wrapper that you might use in your app: | ||
|
||
|
||
```swift | ||
private var cache = [String: FormatString]() | ||
private let queue = DispatchQueue(label: "com.Sprinter") | ||
|
||
func localizedString(_ key: String, _ args: Any...) -> String { | ||
do { | ||
var formatString: FormatString? | ||
queue.sync { formatString = cache[key] } | ||
if formatString == nil { | ||
formatString = try FormatString(NSLocalizedString(key, comment: ""), locale: Locale.current) | ||
queue.async { cache[key] = formatString } | ||
} | ||
return try formatString?.print(arguments: args) ?? "" | ||
} catch { | ||
// Crash in development, but not in production | ||
assertionFailure("\(error)") | ||
return "" | ||
} | ||
} | ||
``` | ||
|
||
This function provides: | ||
|
||
* A convenient API for displaying keys from your `Localizable.strings` file | ||
* Encapsulated error handling, which will crash in development but fail gracefully in production | ||
* Thread-safe caching of `FormatString` instances for better performance | ||
|
||
This is just an example approach, but it should work for most typical use cases. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>$(DEVELOPMENT_LANGUAGE)</string> | ||
<key>CFBundleExecutable</key> | ||
<string>$(EXECUTABLE_NAME)</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>$(PRODUCT_NAME)</string> | ||
<key>CFBundlePackageType</key> | ||
<string>FMWK</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>0.1.0</string> | ||
<key>CFBundleVersion</key> | ||
<string>$(CURRENT_PROJECT_VERSION)</string> | ||
<key>NSHumanReadableCopyright</key> | ||
<string>Copyright © 2017 Nick Lockwood. All rights reserved.</string> | ||
<key>NSPrincipalClass</key> | ||
<string></string> | ||
</dict> | ||
</plist> |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// | ||
// Sprinter.h | ||
// Sprinter | ||
// | ||
// Created by Nick Lockwood on 20/11/2017. | ||
// Copyright © 2017 Nick Lockwood. All rights reserved. | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
//! Project version number for Sprinter. | ||
FOUNDATION_EXPORT double SprinterVersionNumber; | ||
|
||
//! Project version string for Sprinter. | ||
FOUNDATION_EXPORT const unsigned char SprinterVersionString[]; | ||
|
||
// In this header, you should import all the public headers of your framework using statements like #import <Sprinter/PublicHeader.h> | ||
|
||
|
Oops, something went wrong.