Commit 1f9ce46d authored by Thibault Wittemberg's avatar Thibault Wittemberg

project: add SwiftGen code generation

This commit adds the SwiftGen build phase. This phase launches the
swiftgen.sh script that uses SwiftGen tool to produce Swift Code
that make Strings, Assets and Storyboards usage a lot safer

Change-Id: I78471f5603864608e25bbad36f0459103d6bdded
parent 14b092a9
This diff is collapsed.
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
"HomeTabBarTitle" = "Home";
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
"Yesterday" = "Yesterday";
"UserFound" = "User found";
"Conversations" = "Conversations";
"Searching" = "Searching...";
"NoResults" = "No results";
// Generated using SwiftGen, by O.Halligon — https://github.com/AliSoftware/SwiftGen
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit.UIImage
public typealias Image = UIImage
#elseif os(OSX)
import AppKit.NSImage
public typealias Image = NSImage
#endif
private class RingImagesBundleToken {}
// swiftlint:disable file_length
// swiftlint:disable line_length
// swiftlint:disable type_body_length
public enum RingAsset: String {
case icContactPicture = "ic_contact_picture"
case logoRingBeta2Blanc = "logo-ring-beta2-blanc"
/**
Loads from application's Bundle if image exists, then loads from current bundle, fatalError if image does not exist
*/
public var smartImage: Image {
if let appimage = Image(named: self.rawValue, in: nil, compatibleWith: nil) {
return appimage
} else if let fmkImage = Image(named: self.rawValue, in: Bundle(for: RingImagesBundleToken.self), compatibleWith: nil) {
return fmkImage
} else {
fatalError("Impossible to load image \(self.rawValue)")
}
}
var image: Image {
if let img = Image(named: self.rawValue, in: Bundle(for: RingImagesBundleToken.self), compatibleWith: nil) {
return img
}
fatalError("Impossible to load image \(self.rawValue)")
}
}
// swiftlint:enable type_body_length
public extension Image {
convenience init!(asset: RingAsset) {
self.init(named: asset.rawValue)
}
}
// Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen
import Foundation
import UIKit
// swiftlint:disable file_length
// swiftlint:disable line_length
// swiftlint:disable type_body_length
protocol StoryboardSceneType {
static var storyboardName: String { get }
}
extension StoryboardSceneType {
static func storyboard() -> UIStoryboard {
return UIStoryboard(name: self.storyboardName, bundle: Bundle(for: BundleToken.self))
}
static func initialViewController() -> UIViewController {
guard let vc = storyboard().instantiateInitialViewController() else {
fatalError("Failed to instantiate initialViewController for \(self.storyboardName)")
}
return vc
}
}
extension StoryboardSceneType where Self: RawRepresentable, Self.RawValue == String {
func viewController() -> UIViewController {
return Self.storyboard().instantiateViewController(withIdentifier: self.rawValue)
}
static func viewController(identifier: Self) -> UIViewController {
return identifier.viewController()
}
}
protocol StoryboardSegueType: RawRepresentable { }
extension UIViewController {
func perform<S: StoryboardSegueType>(segue: S, sender: Any? = nil) where S.RawValue == String {
performSegue(withIdentifier: segue.rawValue, sender: sender)
}
}
enum StoryboardScene {
enum LaunchScreen: StoryboardSceneType {
static let storyboardName = "LaunchScreen"
}
enum Main: StoryboardSceneType {
static let storyboardName = "Main"
static func initialViewController() -> Ring.MainTabBarViewController {
guard let vc = storyboard().instantiateInitialViewController() as? Ring.MainTabBarViewController else {
fatalError("Failed to instantiate initialViewController for \(self.storyboardName)")
}
return vc
}
}
enum WalkthroughStoryboard: StoryboardSceneType {
static let storyboardName = "WalkthroughStoryboard"
static func initialViewController() -> UINavigationController {
guard let vc = storyboard().instantiateInitialViewController() as? UINavigationController else {
fatalError("Failed to instantiate initialViewController for \(self.storyboardName)")
}
return vc
}
}
}
enum StoryboardSegue {
enum Main: String, StoryboardSegueType {
case showMessages = "ShowMessages"
case accountDetails
}
enum WalkthroughStoryboard: String, StoryboardSegueType {
case accountToPermissionsSegue = "AccountToPermissionsSegue"
case createProfileSegue = "CreateProfileSegue"
case linkDeviceToAccountSegue = "LinkDeviceToAccountSegue"
case profileToAccountSegue = "ProfileToAccountSegue"
case profileToLinkSegue = "ProfileToLinkSegue"
}
}
private final class BundleToken {}
// Generated using SwiftGen, by O.Halligon — https://github.com/AliSoftware/SwiftGen
import Foundation
private class RingStringsBundleToken {}
// swiftlint:disable file_length
// swiftlint:disable line_length
// swiftlint:disable type_body_length
// swiftlint:disable nesting
// swiftlint:disable variable_name
// swiftlint:disable valid_docs
enum L10n {
/// Account Added
static let accountAddedTitle = L10n.tr("AccountAddedTitle")
/// Can't find account
static let accountCannotBeFoundTitle = L10n.tr("AccountCannotBeFoundTitle")
/// The account couldn't be created.
static let accountDefaultErrorMessage = L10n.tr("AccountDefaultErrorMessage")
/// Unknown error
static let accountDefaultErrorTitle = L10n.tr("AccountDefaultErrorTitle")
/// Could not add account because Ring couldn't connect to the distributed network. Check your device connectivity.
static let accountNoNetworkMessage = L10n.tr("AccountNoNetworkMessage")
/// Can't connect to the network
static let accountNoNetworkTitle = L10n.tr("AccountNoNetworkTitle")
/// Account couldn't be found on the Ring network. Make sure it was exported on Ring from an existing device, and that provided credentials are correct.
static let acountCannotBeFoundMessage = L10n.tr("AcountCannotBeFoundMessage")
/// Choose strong password you will remember to protect your Ring account.
static let chooseStrongPassword = L10n.tr("ChooseStrongPassword")
/// Conversations
static let conversations = L10n.tr("Conversations")
/// Create a Ring account
static let createAccount = L10n.tr("CreateAccount")
/// Create your Ring account
static let createAccountFormTitle = L10n.tr("CreateAccountFormTitle")
/// Enter new username
static let enterNewUsernamePlaceholder = L10n.tr("EnterNewUsernamePlaceholder")
/// Home
static let homeTabBarTitle = L10n.tr("HomeTabBarTitle")
/// Invalid username
static let invalidUsername = L10n.tr("InvalidUsername")
/// Link this device to an account
static let linkDeviceButton = L10n.tr("LinkDeviceButton")
/// Looking for username availability...
static let lookingForUsernameAvailability = L10n.tr("LookingForUsernameAvailability")
/// New Password
static let newPasswordPlaceholder = L10n.tr("NewPasswordPlaceholder")
/// No results
static let noResults = L10n.tr("NoResults")
/// 6 characters minimum
static let passwordCharactersNumberError = L10n.tr("PasswordCharactersNumberError")
/// Passwords do not match
static let passwordNotMatchingError = L10n.tr("PasswordNotMatchingError")
/// Register public username (experimental)
static let registerPublicUsername = L10n.tr("RegisterPublicUsername")
/// Repeat new password
static let repeatPasswordPlaceholder = L10n.tr("RepeatPasswordPlaceholder")
/// Searching...
static let searching = L10n.tr("Searching")
/// User found
static let userFound = L10n.tr("UserFound")
/// Username already taken
static let usernameAlreadyTaken = L10n.tr("UsernameAlreadyTaken")
/// Adding account
static let waitCreateAccountTitle = L10n.tr("WaitCreateAccountTitle")
/// A Ring account allows you to reach people securely in peer to peer through fully distributed network
static let welcomeText = L10n.tr("WelcomeText")
/// Welcome to Ring
static let welcomeTitle = L10n.tr("WelcomeTitle")
/// Yesterday
static let yesterday = L10n.tr("Yesterday")
}
struct LocalizableString {
let key: String
let args: [CVarArg]
/**
Returns String from Current Bundle
*/
public var string: String {
let format: String = NSLocalizedString(key, tableName: nil, bundle: Bundle(for: RingStringsBundleToken.self), value: "", comment: "")
return String(format: format, locale: Locale.current, arguments: args)
}
/**
Returns String translated from App's Bundle is found, otherwise from Current Bundle
*/
public var smartString: String {
// Load from App's Bundle first
var format: String = NSLocalizedString(key, tableName: nil, bundle: Bundle.main, value: "", comment: "")
if format != "" && format != key {
return String(format: format, locale: Locale.current, arguments: args)
}
// Load from Current Bundle
format = NSLocalizedString(key, tableName: nil, bundle: Bundle(for: RingStringsBundleToken.self), value: "", comment: "")
return String(format: format, locale: Locale.current, arguments: args)
}
}
extension L10n {
fileprivate static func tr(_ key: String, _ args: CVarArg...) -> LocalizableString {
return LocalizableString(key: key, args: args)
}
}
// swiftlint:enable type_body_length
// swiftlint:enable nesting
// swiftlint:enable variable_name
// swiftlint:enable valid_docs
......@@ -18,6 +18,20 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
// Global
"HomeTabBarTitle" = "Home";
// Smartlist
"Yesterday" = "Yesterday";
"UserFound" = "User found";
"Conversations" = "Conversations";
"Searching" = "Searching...";
"NoResults" = "No results";
// Walkthrough
//Welcome Screen
"WelcomeTitle" = "Welcome to Ring";
......
// Generated using SwiftGen, by O.Halligon — https://github.com/AliSoftware/SwiftGen
{% if images %}
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit.UIImage
public typealias Image = UIImage
#elseif os(OSX)
import AppKit.NSImage
public typealias Image = NSImage
#endif
private class RingImagesBundleToken {}
// swiftlint:disable file_length
// swiftlint:disable line_length
// swiftlint:disable type_body_length
public enum Ring{{enumName}}: String {
{% for image in images %}
case {{image|swiftIdentifier|snakeToCamelCase|lowerFirstWord}} = "{{image}}"
{% endfor %}
/**
Loads from application's Bundle if image exists, then loads from current bundle, fatalError if image does not exist
*/
public var smartImage: Image {
if let appimage = Image(named: self.rawValue, in: nil, compatibleWith: nil) {
return appimage
} else if let fmkImage = Image(named: self.rawValue, in: Bundle(for: RingImagesBundleToken.self), compatibleWith: nil) {
return fmkImage
} else {
fatalError("Impossible to load image \(self.rawValue)")
}
}
var image: Image {
if let img = Image(named: self.rawValue, in: Bundle(for: RingImagesBundleToken.self), compatibleWith: nil) {
return img
}
fatalError("Impossible to load image \(self.rawValue)")
}
}
// swiftlint:enable type_body_length
public extension Image {
convenience init!(asset: Ring{{enumName}}) {
self.init(named: asset.rawValue)
}
}
{% else %}
// No image found
{% endif %}
// Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen
{% macro className scene %}{% if scene.customClass %}{% if scene.customModule %}{{scene.customModule}}.{% endif %}{{scene.customClass}}{% else %}UI{{scene.baseType}}{% endif %}{% endmacro %}
import Foundation
import UIKit
{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %}
import {{module}}
{% endfor %}
{% if storyboards %}
// swiftlint:disable file_length
// swiftlint:disable line_length
// swiftlint:disable type_body_length
{# This first part of the code is static, same every time whatever Storyboard you have #}
protocol StoryboardSceneType {
static var storyboardName: String { get }
}
extension StoryboardSceneType {
static func storyboard() -> UIStoryboard {
return UIStoryboard(name: self.storyboardName, bundle: Bundle(for: BundleToken.self))
}
static func initialViewController() -> UIViewController {
guard let vc = storyboard().instantiateInitialViewController() else {
fatalError("Failed to instantiate initialViewController for \(self.storyboardName)")
}
return vc
}
}
extension StoryboardSceneType where Self: RawRepresentable, Self.RawValue == String {
func viewController() -> UIViewController {
return Self.storyboard().instantiateViewController(withIdentifier: self.rawValue)
}
static func viewController(identifier: Self) -> UIViewController {
return identifier.viewController()
}
}
protocol StoryboardSegueType: RawRepresentable { }
extension UIViewController {
func perform<S: StoryboardSegueType>(segue: S, sender: Any? = nil) where S.RawValue == String {
performSegue(withIdentifier: segue.rawValue, sender: sender)
}
}
{# This is where the generation begins, this code depends on what you have in your Storyboards #}
{% set sceneEnumName %}{{param.sceneEnumName|default:"StoryboardScene"}}{% endset %}
enum {{sceneEnumName}} {
{% for storyboard in storyboards %}
{% set storyboardName %}{{storyboard.name|swiftIdentifier|escapeReservedKeywords}}{% endset %}
{% if storyboard.scenes %}
enum {{storyboardName}}: String, StoryboardSceneType {
static let storyboardName = "{{storyboard.name}}"
{% if storyboard.initialScene and storyboard.initialScene.baseType != "ViewController" %}
{% set initialSceneClass %}{% call className storyboard.initialScene %}{% endset %}
static func initialViewController() -> {{initialSceneClass}} {
guard let vc = storyboard().instantiateInitialViewController() as? {{initialSceneClass}} else {
fatalError("Failed to instantiate initialViewController for \(self.storyboardName)")
}
return vc
}
{% endif %}
{% for scene in storyboard.scenes %}
{% set sceneID %}{{scene.identifier|swiftIdentifier|snakeToCamelCase|lowerFirstWord}}{% endset %}
case {{sceneID}}Scene = "{{scene.identifier}}"
{% if scene.baseType != "ViewController" %}
{% set sceneClass %}{% call className scene %}{% endset %}
static func instantiate{{sceneID|snakeToCamelCase}}() -> {{sceneClass}} {
guard let vc = {{sceneEnumName}}.{{storyboardName}}.{{sceneID}}Scene.viewController() as? {{sceneClass}}
else {
fatalError("ViewController '{{scene.identifier}}' is not of the expected class {{sceneClass}}.")
}
return vc
}
{% else %}
static func instantiate{{sceneID|snakeToCamelCase}}() -> UIViewController {
return {{sceneEnumName}}.{{storyboardName}}.{{sceneID}}Scene.viewController()
}
{% endif %}
{% endfor %}
}
{% else %}
enum {{storyboardName}}: StoryboardSceneType {
static let storyboardName = "{{storyboard.name}}"
{% if storyboard.initialScene and storyboard.initialScene.baseType != "ViewController" %}
{% set initialSceneClass %}{% call className storyboard.initialScene %}{% endset %}
static func initialViewController() -> {{initialSceneClass}} {
guard let vc = storyboard().instantiateInitialViewController() as? {{initialSceneClass}} else {
fatalError("Failed to instantiate initialViewController for \(self.storyboardName)")
}
return vc
}
{% endif %}
}
{% endif %}
{% endfor %}
}
enum {{param.segueEnumName|default:"StoryboardSegue"}} {
{% for storyboard in storyboards where storyboard.segues %}
enum {{storyboard.name|swiftIdentifier|escapeReservedKeywords}}: String, StoryboardSegueType {
{% for segue in storyboard.segues %}
{% set segueID %}{{segue.identifier|swiftIdentifier|snakeToCamelCase|lowerFirstWord}}{% endset %}
case {{segueID|escapeReservedKeywords}}{% if segueID != segue.identifier %} = "{{segue.identifier}}"{% endif %}
{% endfor %}
}
{% endfor %}
}
private final class BundleToken {}
{% else %}
// No storyboard found
{% endif %}
// Generated using SwiftGen, by O.Halligon — https://github.com/AliSoftware/SwiftGen
{% if structuredStrings %}
import Foundation
private class RingStringsBundleToken {}
// swiftlint:disable file_length
// swiftlint:disable line_length
// swiftlint:disable type_body_length
// swiftlint:disable nesting
// swiftlint:disable variable_name
// swiftlint:disable valid_docs
{% macro parametersBlock params %}{% for type in params.types %}_ p{{forloop.counter}}: {{type}}{% if not forloop.last %}, {% endif %}{% endfor %}{% endmacro %}
{% macro argumentsBlock params %}{% for type in params.types %}p{{forloop.counter}}{% if not forloop.last %}, {% endif %}{% endfor %}{% endmacro %}
{% macro recursiveBlock strings sp %}
{{sp}} {% for string in strings.strings %}
{{sp}} /// {{string.translation}}
{{sp}} {% if string.params %}
{{sp}} static func {{string.keytail|swiftIdentifier|snakeToCamelCase|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.params %}) -> LocalizableString {
{{sp}} return {{enumName}}.tr("{{string.key}}", {% call argumentsBlock string.params %})
{{sp}} }
{{sp}} {% else %}
{{sp}} static let {{string.keytail|swiftIdentifier|snakeToCamelCase|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{string.key}}")
{{sp}} {% endif %}
{{sp}} {% endfor %}
{{sp}} {% if strings.subenums %}
{{sp}} {% for subenum in strings.subenums %}
{{sp}} enum {{subenum.name|swiftIdentifier|snakeToCamelCase}} {
{{sp}} {% set sp2 %}{{sp}} {% endset %}
{{sp}} {% call recursiveBlock subenum sp2 %}
{{sp}} }
{{sp}} {% endfor %}
{{sp}} {% endif %}
{% endmacro %}
enum {{enumName}} {
{% call recursiveBlock structuredStrings sp %}
}
struct LocalizableString {
let key: String
let args: [CVarArg]
/**
Returns String from Current Bundle
*/
public var string: String {
let format: String = NSLocalizedString(key, tableName: nil, bundle: Bundle(for: RingStringsBundleToken.self), value: "", comment: "")
return String(format: format, locale: Locale.current, arguments: args)
}
/**
Returns String translated from App's Bundle is found, otherwise from Current Bundle
*/
public var smartString: String {
// Load from App's Bundle first
var format: String = NSLocalizedString(key, tableName: nil, bundle: Bundle.main, value: "", comment: "")
if format != "" && format != key {
return String(format: format, locale: Locale.current, arguments: args)
}
// Load from Current Bundle
format = NSLocalizedString(key, tableName: nil, bundle: Bundle(for: RingStringsBundleToken.self), value: "", comment: "")
return String(format: format, locale: Locale.current, arguments: args)
}
}
extension {{enumName}} {
fileprivate static func tr(_ key: String, _ args: CVarArg...) -> LocalizableString {
return LocalizableString(key: key, args: args)
}
}
// swiftlint:enable type_body_length
// swiftlint:enable nesting
// swiftlint:enable variable_name
// swiftlint:enable valid_docs
{% else %}
// No string found
{% endif %}
#!/bin/bash
EXPECTED_VERSION="SwiftGen v4.2.1 (Stencil v0.9.0, StencilSwiftKit v1.0.2, SwiftGenKit v1.1.0)"
# Here execute the various SwiftGen commands you need
run_swiftgen() {
if [ ! "$PROJECT_DIR" -o ! $"PROJECT_NAME" ]; then echo "Some variables are not set. Please run from an Xcode build phase"; exit 1; fi
SRCDIR="$PROJECT_DIR/$PROJECT_NAME"
OUTDIR="$SRCDIR/Constants/Generated"
TPLDIR=$(dirname $0)
echo "SwiftGen: Generating files..."
swiftgen storyboards "$SRCDIR" -p "$TPLDIR/storyboards.stencil" --output "$OUTDIR/Storyboards.swift"
swiftgen images "$SRCDIR/Resources/Images.xcassets" -p "$TPLDIR/images.stencil" --output "$OUTDIR/Images.swift"
swiftgen strings "$SRCDIR/Resources/en.lproj/Localizable.strings" -p "$TPLDIR/strings.stencil" --output "$OUTDIR/Strings.swift"
}
# Main script to check if SwiftGen is installed, check the version, and run it only if version matches
if which swiftgen >/dev/null; then
CURRENT_VERSION=`swiftgen --version`
if [ "$CURRENT_VERSION" != "$EXPECTED_VERSION" ]; then
echo "error: SwiftGen version mismatch (expected ${EXPECTED_VERSION%% \(*\)}, got ${CURRENT_VERSION%% \(*\)})"
exit 1
fi
run_swiftgen
else
echo "warning: SwiftGen not installed, download it from https://github.com/SwiftGen/SwiftGen"
fi
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment