Skip to content

Commit

Permalink
SPM Conversion (#6)
Browse files Browse the repository at this point in the history
Overview of changes:

- Converted the repo to a Swift package
- dropped CocoaPods support, since SPM and Mint are now supported
- updated installation instructions
- Simplified Makefile to use swift test
- Greatly improved argument parsing by using Apple's ArgumentParser package (also removed unit tests for parsing)
- Writing .strings files now also works if the directory/file doesn't exist yet
- Added Github Actions for running tests on push/PR
  • Loading branch information
henrik-dmg authored Dec 2, 2020
1 parent 2cc45ce commit 96b2eb9
Show file tree
Hide file tree
Showing 41 changed files with 352 additions and 1,006 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Swift

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: macos-latest

steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- uses: actions/checkout@v2
- name: Build
run: swift build -v
- name: Run tests
run: swift test -v
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,4 @@ fastlane/test_output
*.xcarchive/
Products/
.DS_Store
.swiftpm
2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.1
5.3
13 changes: 8 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
test:
xcodebuild test -project "SwiftGenStrings.xcodeproj" -scheme "SwiftGenStrings" -destination "platform=macOS"

release:
xcodebuild -scheme "SwiftGenStrings" -configuration "Release" -destination "generic/platform=macOS" clean archive -archivePath "build/"
swift build -c release -v
mkdir -p Products
cp build.xcarchive/Products/usr/local/bin/SwiftGenStrings Products/
cp .build/release/SwiftGenStrings Products/SwiftGenStrings

install: release
install -v Products/SwiftGenStrings /usr/local/bin/SwiftGenStrings

test:
swift test -v
16 changes: 16 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": {
"branch": null,
"revision": "92646c0cdbaca076c8d3d0207891785b3379cbff",
"version": "0.3.1"
}
}
]
},
"version": 1
}
43 changes: 43 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "SwiftGenStrings",
platforms: [
.macOS(.v10_12)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.executable(
name: "SwiftGenStrings",
targets: ["SwiftGenStrings"]
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.3.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "SwiftGenStrings",
dependencies: [
"SwiftGenStringsCore",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.target(
name: "SwiftGenStringsCore",
dependencies: []
),
.testTarget(
name: "SwiftGenStringsTests",
dependencies: ["SwiftGenStringsCore"],
resources: [
.process("Resources")
]
),
]
)
52 changes: 23 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,17 @@ The upstream issue is tracked [here](https://openradar.appspot.com/22133811).
## Usage

```
SwiftGenStrings files
SwiftGenStrings [-s <routine>] [-o <outputDir>] files
SwiftGenStrings [-h|--help]
OPTIONS
-h|--help
(Optional) Print help.
-s routine
(Optional) Substitute routine for NSLocalizedString, useful when different macro is used.
-o outputDir
(Optional) Specifies what directory Localizable.strings table is created in.
Not specifying output directory will print script output content to standard output (console).
files
List of files, that are used as source of Localizable.strings generation.
SwiftGenStrings [<files> ...] [-s <substitute>] [-o <output-directory>]
ARGUMENTS:
<files> List of files, that are used as source of Localizable.strings generation.
OPTIONS:
-s <substitute> (Optional) Substitute for NSLocalizedString, useful when different macro is used.
-o <output-directory> (Optional) Specifies what directory Localizable.strings table is created in. Not specifying output directory will print script output content
to standard output (console).
--version Show the version.
-h, --help Show help information.
```

To gather strings in current directory, run:
Expand All @@ -39,6 +36,13 @@ $ find . \( -name "*.swift" ! -path "./Carthage/*" ! -path "./Pods/*" \) | xargs

## Installation

### [Mint](https://github.com/yonaskolb/mint)

The quickest and easiest way to install SwiftGenStrings is via Mint
```
$ mint install kayak/SwiftGenStrings
```

### Prebuilt Binaries

We tag releases and upload prebuilt binaries to GitHub. Checkout the [releases](https://github.com/kayak/SwiftGenStrings/releases) tab or go straight to the [latest](https://github.com/kayak/SwiftGenStrings/releases/latest) release.
Expand All @@ -51,29 +55,19 @@ The project provides a `Makefile`. To export a binary run:
$ make release
```

The exported binary can be found under `Products/SwiftGenStrings`

### CocoaPods

A podspec file for the project was released (see [here](https://cocoapods.org/pods/SwiftGenStrings)). To consume the project, simply add the following to your `Podfile`:

```
pod 'SwiftGenStrings'
```

After running `pod install` or `pod update`, you will then find the binary under `Pods/SwiftGenStrings/SwiftGenStrings`
The exported binary can be found under `Products/SwiftGenStrings`. Alternatively you can use `make install` to install the compiled library directly into `/usr/local/bin/SwiftGenStrings`

## Testing

Xcode 9.2 seems to have a bug with running tests against macOS destination, luckily, `xcodebuild` works just fine:
Since SwiftGenStrings is a SPM package, running tests is easy:
```
$ make test
$ swift test
```

## Requirements

- Xcode 9.2
- Swift 4.0.2
- Xcode 12
- Swift 5.3

## Limitations

Expand Down
15 changes: 15 additions & 0 deletions Sources/SwiftGenStrings/URL+ArgumentParser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ArgumentParser
import Foundation

extension URL: ExpressibleByArgument {

public init?(argument: String) {
if argument.hasPrefix("~/") {
let cleanedPath = String(argument.dropFirst())
self.init(fileURLWithPath: cleanedPath, relativeTo: FileManager.default.homeDirectoryForCurrentUser)
} else {
self.init(fileURLWithPath: argument)
}
}

}
72 changes: 72 additions & 0 deletions Sources/SwiftGenStrings/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import ArgumentParser
import Foundation
import SwiftGenStringsCore

struct SwiftGenStrings: ParsableCommand {

private static var abstract = """
SwiftGenStrings is a command line application that can be used as a drop-in replacement for the standard genstrings command for Swift sources.
The latter only supports the short form of the NSLocalizedString function but breaks as soon as you use any parameters other than key and comment as in
"""

static let configuration = CommandConfiguration(
commandName: "SwiftGenStrings",
abstract: SwiftGenStrings.abstract,
version: "0.0.2",
helpNames: .shortAndLong
)

@Argument(help: "List of files, that are used as source of Localizable.strings generation.")
var files: [URL]

@Option(name: .short, help: "(Optional) Substitute for NSLocalizedString, useful when different macro is used.")
var substitute: String?

@Option(name: .short, help: "(Optional) Specifies what directory Localizable.strings table is created in. Not specifying output directory will print script output content to standard output (console).")
var outputDirectory: URL?

func run() throws {
do {
try ky_run()
} catch let error as NSError {
ErrorFormatter().writeFormattedError(error.localizedDescription)
Darwin.exit(Int32(error.code))
}
}

private func ky_run() throws {
let collectionErrorOutput = LocalizedStringCollectionStandardErrorOutput()
let finalStrings = LocalizedStringCollection(strings: [], errorOutput: collectionErrorOutput)

let tokenizer = SwiftTokenizer()

var numberOfWrittenErrors = 0
for file in files {
let contents = try String(contentsOf: file)
let tokens = tokenizer.tokenizeSwiftString(contents)
let errorOutput = LocalizedStringFinderStandardErrorOutput(fileURL: file)
let finder = LocalizedStringFinder(routine: substitute ?? "NSLocalizedString", errorOutput: errorOutput)
let strings = finder.findLocalizedStrings(tokens)
let collection = LocalizedStringCollection(strings: strings, errorOutput: collectionErrorOutput)
finalStrings.merge(with: collection)
numberOfWrittenErrors += errorOutput.numberOfWrittenErrors + collectionErrorOutput.numberOfWrittenErrors
}

guard numberOfWrittenErrors == 0 else {
let errorMessage = numberOfWrittenErrors == 1 ? "1 error was written" : "\(numberOfWrittenErrors) errors were written"
throw NSError(description: errorMessage)
}

let output = finalStrings.formattedContent

if let outputDirectory = outputDirectory {
let outputFileURL = outputDirectory.appendingPathComponent("Localizable.strings")
try output.ky_write(to: outputFileURL, atomically: false, encoding: .utf8)
} else {
print(output, terminator: "") // No newline at the end
}
}

}

SwiftGenStrings.main()
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Foundation

class CharacterIterator {
public class CharacterIterator {

private let characters: [Character]

private var index = -1

init(string: String) {
public init(string: String) {
self.characters = string.map { $0 }
}

Expand Down
10 changes: 10 additions & 0 deletions Sources/SwiftGenStringsCore/Extensions/NSError+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

public extension NSError {

convenience init(domain: String = "com.kayak.travel.SwiftGenStrings", code: Int = 1, description: String) {
let userInfo = [NSLocalizedDescriptionKey: description]
self.init(domain: domain, code: code, userInfo: userInfo)
}

}
10 changes: 10 additions & 0 deletions Sources/SwiftGenStringsCore/Extensions/String+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

public extension String {

func ky_write(to url: URL, atomically: Bool, encoding: Encoding = .utf8) throws {
try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil)
try write(to: url, atomically: atomically, encoding: .utf8)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private let formatSpecifierRegex: NSRegularExpression = {
return try! NSRegularExpression(pattern: pattern, options:[])
}()

class LocalizedString: CustomStringConvertible, Equatable {
public class LocalizedString: CustomStringConvertible, Equatable {

let key: String
let value: String
Expand Down Expand Up @@ -46,24 +46,24 @@ class LocalizedString: CustomStringConvertible, Equatable {
}

var formatted: String {
return "/* \(formattedComments) */\n" +
"/* \(formattedComments) */\n" +
"\"\(key)\" = \"\(valueWithIndexedPlaceholders)\";"
}

private var formattedComments: String {
return Array(Set(comments)).sorted().joined(separator: "\n ")
Array(Set(comments)).sorted().joined(separator: "\n ")
}

// MARK: - CustomStringConvertible

var description: String {
return "LocalizedString(key: \(key), value: \(value), comments: \(comments))"
public var description: String {
"LocalizedString(key: \(key), value: \(value), comments: \(comments))"
}

// MARK: - Equatable

static func ==(lhs: LocalizedString, rhs: LocalizedString) -> Bool {
return lhs.key == rhs.key && lhs.value == rhs.value && lhs.comments == rhs.comments
public static func ==(lhs: LocalizedString, rhs: LocalizedString) -> Bool {
lhs.key == rhs.key && lhs.value == rhs.value && lhs.comments == rhs.comments
}

}
Loading

0 comments on commit 96b2eb9

Please sign in to comment.