Skip to content

Instantly share code, notes, and snippets.

@satishbabariya
Created March 12, 2018 06:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save satishbabariya/fdf6d25571e7e00804fd02121a718908 to your computer and use it in GitHub Desktop.
Save satishbabariya/fdf6d25571e7e00804fd02121a718908 to your computer and use it in GitHub Desktop.
Localization Generetor
set -x
#--------- START OF CONFIGURATION
# Get base path to project
BASE_PATH="$PROJECT_DIR/$PROJECT_NAME"
# Get path to Laurine Generator script
LAURINE_PATH="$BASE_PATH/LocalizationsGenerator.swift"
# Get path to main localization file (usually english).
SOURCE_PATH="$BASE_PATH/Resource/Base.lproj/Localizable.strings"
# Get path to output. If you use ObjC version of output, set implementation file (.m), as header will be generated automatically
OUTPUT_PATH="$BASE_PATH/Resource/Localizations.swift"
#--------- END OF CONFIGURATION
# Add permission to generator for script execution
chmod 755 "$LAURINE_PATH"
# Actually generate output. -- CUSTOMIZE -- parameters to your needs (see documentation).
# Will only re-generate script if something changed
#if [ "$OUTPUT_PATH" -ot "$SOURCE_PATH" ]; then
"$LAURINE_PATH" -i "$SOURCE_PATH" -o "$OUTPUT_PATH"
#fi
#!/usr/bin/env xcrun -sdk macosx swift
import Foundation
/* Required for setlocale(3) */
#if os(OSX)
import Darwin
#elseif os(Linux)
import Glibc
#endif
let ShortOptionPrefix = "-"
let LongOptionPrefix = "--"
/* Stop parsing arguments when an ArgumentStopper (--) is detected. This is a GNU getopt
* convention; cf. https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html
*/
let ArgumentStopper = "--"
/* Allow arguments to be attached to flags when separated by this character.
* --flag=argument is equivalent to --flag argument
*/
let ArgumentAttacher: Character = "="
/* An output stream to stderr; used by CommandLine.printUsage(). */
private struct StderrOutputStream: TextOutputStream {
static let stream = StderrOutputStream()
func write(_ s: String) {
fputs(s, stderr)
}
}
/**
* The CommandLine class implements a command-line interface for your app.
*
* To use it, define one or more Options (see Option.swift) and add them to your
* CommandLine object, then invoke `parse()`. Each Option object will be populated with
* the value given by the user.
*
* If any required options are missing or if an invalid value is found, `parse()` will throw
* a `ParseError`. You can then call `printUsage()` to output an automatically-generated usage
* message.
*/
open class CommandLine {
fileprivate var _arguments: [String]
fileprivate var _options: [Option] = [Option]()
fileprivate var _maxFlagDescriptionWidth: Int = 0
fileprivate var _usedFlags: Set<String> {
var usedFlags = Set<String>(minimumCapacity: _options.count * 2)
for option in _options {
for case let flag? in [option.shortFlag, option.longFlag] {
usedFlags.insert(flag)
}
}
return usedFlags
}
/**
* After calling `parse()`, this property will contain any values that weren't captured
* by an Option. For example:
*
* ```
* let cli = CommandLine()
* let fileType = StringOption(shortFlag: "t", longFlag: "type", required: true, helpMessage: "Type of file")
*
* do {
* try cli.parse()
* print("File type is \(type), files are \(cli.unparsedArguments)")
* catch {
* cli.printUsage(error)
* exit(EX_USAGE)
* }
*
* ---
*
* $ ./readfiles --type=pdf ~/file1.pdf ~/file2.pdf
* File type is pdf, files are ["~/file1.pdf", "~/file2.pdf"]
* ```
*/
open fileprivate(set) var unparsedArguments: [String] = [String]()
/**
* If supplied, this function will be called when printing usage messages.
*
* You can use the `defaultFormat` function to get the normally-formatted
* output, either before or after modifying the provided string. For example:
*
* ```
* let cli = CommandLine()
* cli.formatOutput = { str, type in
* switch(type) {
* case .Error:
* // Make errors shouty
* return defaultFormat(str.uppercaseString, type: type)
* case .OptionHelp:
* // Don't use the default indenting
* return ">> \(s)\n"
* default:
* return defaultFormat(str, type: type)
* }
* }
* ```
*
* - note: Newlines are not appended to the result of this function. If you don't use
* `defaultFormat()`, be sure to add them before returning.
*/
open var formatOutput: ((String, OutputType) -> String)?
/**
* The maximum width of all options' `flagDescription` properties; provided for use by
* output formatters.
*
* - seealso: `defaultFormat`, `formatOutput`
*/
open var maxFlagDescriptionWidth: Int {
if _maxFlagDescriptionWidth == 0 {
_maxFlagDescriptionWidth = _options.map { $0.flagDescription.count }.sorted().first ?? 0
}
return _maxFlagDescriptionWidth
}
/**
* The type of output being supplied to an output formatter.
*
* - seealso: `formatOutput`
*/
public enum OutputType {
/** About text: `Usage: command-example [options]` and the like */
case about
/** An error message: `Missing required option --extract` */
case error
/** An Option's `flagDescription`: `-h, --help:` */
case optionFlag
/** An Option's help message */
case optionHelp
}
/** A ParseError is thrown if the `parse()` method fails. */
public enum ParseError: Error, CustomStringConvertible {
/** Thrown if an unrecognized argument is passed to `parse()` in strict mode */
case InvalidArgument(String)
/** Thrown if the value for an Option is invalid (e.g. a string is passed to an IntOption) */
case InvalidValueForOption(Option, [String])
/** Thrown if an Option with required: true is missing */
case MissingRequiredOptions([Option])
public var description: String {
switch self {
case let .InvalidArgument(arg):
return "Invalid argument: \(arg)"
case let .InvalidValueForOption(opt, vals):
let vs = vals.joined(separator: ", ")
return "Invalid value(s) for option \(opt.flagDescription): \(vs)"
case let .MissingRequiredOptions(opts):
return "Missing required options: \(opts.map { return $0.flagDescription })"
}
}
}
/**
* Initializes a CommandLine object.
*
* - parameter arguments: Arguments to parse. If omitted, the arguments passed to the app
* on the command line will automatically be used.
*
* - returns: An initalized CommandLine object.
*/
public init(arguments: [String] = Swift.CommandLine.arguments) {
self._arguments = arguments
/* Initialize locale settings from the environment */
setlocale(LC_ALL, "")
}
/* Returns all argument values from flagIndex to the next flag or the end of the argument array. */
private func _getFlagValues(_ flagIndex: Int, _ attachedArg: String? = nil) -> [String] {
var args: [String] = [String]()
var skipFlagChecks = false
if let a = attachedArg {
args.append(a)
}
for i in flagIndex + 1 ..< _arguments.count {
if !skipFlagChecks {
if _arguments[i] == ArgumentStopper {
skipFlagChecks = true
continue
}
if _arguments[i].hasPrefix(ShortOptionPrefix) && Int(_arguments[i]) == nil &&
Double(_arguments[i]) == nil {
break
}
}
args.append(_arguments[i])
}
return args
}
/**
* Adds an Option to the command line.
*
* - parameter option: The option to add.
*/
public func addOption(_ option: Option) {
let uf = _usedFlags
for case let flag? in [option.shortFlag, option.longFlag] {
assert(!uf.contains(flag), "Flag '\(flag)' already in use")
}
_options.append(option)
_maxFlagDescriptionWidth = 0
}
/**
* Adds one or more Options to the command line.
*
* - parameter options: An array containing the options to add.
*/
public func addOptions(_ options: [Option]) {
for o in options {
addOption(o)
}
}
/**
* Adds one or more Options to the command line.
*
* - parameter options: The options to add.
*/
public func addOptions(_ options: Option...) {
for o in options {
addOption(o)
}
}
/**
* Sets the command line Options. Any existing options will be overwritten.
*
* - parameter options: An array containing the options to set.
*/
public func setOptions(_ options: [Option]) {
_options = [Option]()
addOptions(options)
}
/**
* Sets the command line Options. Any existing options will be overwritten.
*
* - parameter options: The options to set.
*/
public func setOptions(_ options: Option...) {
_options = [Option]()
addOptions(options)
}
/**
* Parses command-line arguments into their matching Option values.
*
* - parameter strict: Fail if any unrecognized flags are present (default: false).
*
* - throws: A `ParseError` if argument parsing fails:
* - `.InvalidArgument` if an unrecognized flag is present and `strict` is true
* - `.InvalidValueForOption` if the value supplied to an option is not valid (for
* example, a string is supplied for an IntOption)
* - `.MissingRequiredOptions` if a required option isn't present
*/
open func parse(strict: Bool = false) throws {
var strays = _arguments
/* Nuke executable name */
strays[0] = ""
let argumentsEnumerator = _arguments.enumerated()
for (idx, arg) in argumentsEnumerator {
if arg == ArgumentStopper {
break
}
if !arg.hasPrefix(ShortOptionPrefix) {
continue
}
let skipChars = arg.hasPrefix(LongOptionPrefix) ?
LongOptionPrefix.count : ShortOptionPrefix.count
let flagWithArg = arg[arg.index(arg.startIndex, offsetBy: skipChars)..<arg.endIndex]
/* The argument contained nothing but ShortOptionPrefix or LongOptionPrefix */
if flagWithArg.isEmpty {
continue
}
/* Remove attached argument from flag */
let splitFlag = flagWithArg.split(separator: ArgumentAttacher, maxSplits: 1)
let flag = String(splitFlag[0])
let attachedArg: String? = splitFlag.count == 2 ? String(splitFlag[1]) : nil
var flagMatched = false
for option in _options where option.flagMatch(flag) {
let vals = self._getFlagValues(idx, attachedArg)
guard option.setValue(vals) else {
throw ParseError.InvalidValueForOption(option, vals)
}
var claimedIdx = idx + option.claimedValues
if attachedArg != nil { claimedIdx -= 1 }
for i in idx...claimedIdx {
strays[i] = ""
}
flagMatched = true
break
}
/* Flags that do not take any arguments can be concatenated */
let flagLength = flag.count
if !flagMatched && !arg.hasPrefix(LongOptionPrefix) {
let flagCharactersEnumerator = flag.enumerated()
for (i, c) in flagCharactersEnumerator {
for option in _options where option.flagMatch(String(c)) {
/* Values are allowed at the end of the concatenated flags, e.g.
* -xvf <file1> <file2>
*/
let vals = (i == flagLength - 1) ? self._getFlagValues(idx, attachedArg) : [String]()
guard option.setValue(vals) else {
throw ParseError.InvalidValueForOption(option, vals)
}
var claimedIdx = idx + option.claimedValues
if attachedArg != nil { claimedIdx -= 1 }
for i in idx...claimedIdx {
strays[i] = ""
}
flagMatched = true
break
}
}
}
/* Invalid flag */
guard !strict || flagMatched else {
throw ParseError.InvalidArgument(arg)
}
}
/* Check to see if any required options were not matched */
let missingOptions = _options.filter { $0.required && !$0.wasSet }
guard missingOptions.count == 0 else {
throw ParseError.MissingRequiredOptions(missingOptions)
}
unparsedArguments = strays.filter { $0 != "" }
}
/**
* Provides the default formatting of `printUsage()` output.
*
* - parameter s: The string to format.
* - parameter type: Type of output.
*
* - returns: The formatted string.
* - seealso: `formatOutput`
*/
open func defaultFormat(_ s: String, type: OutputType) -> String {
switch type {
case .about:
return "\(s)\n"
case .error:
return "\(s)\n\n"
case .optionFlag:
return " \(s.padding(toLength: maxFlagDescriptionWidth, withPad: " ", startingAt: 0)):\n"
case .optionHelp:
return " \(s)\n"
}
}
/* printUsage() is generic for OutputStreamType because the Swift compiler crashes
* on inout protocol function parameters in Xcode 7 beta 1 (rdar://21372694).
*/
/**
* Prints a usage message.
*
* - parameter to: An OutputStreamType to write the error message to.
*/
public func printUsage<TargetStream: TextOutputStream>(_ to: inout TargetStream) {
/* Nil coalescing operator (??) doesn't work on closures :( */
let format = formatOutput != nil ? formatOutput! : defaultFormat
let name = _arguments[0]
print(format("Usage: \(name) [options]", .about), terminator: "", to: &to)
for opt in _options {
print(format(opt.flagDescription, .optionFlag), terminator: "", to: &to)
print(format(opt.helpMessage, .optionHelp), terminator: "", to: &to)
}
}
/**
* Prints a usage message.
*
* - parameter error: An error thrown from `parse()`. A description of the error
* (e.g. "Missing required option --extract") will be printed before the usage message.
* - parameter to: An OutputStreamType to write the error message to.
*/
public func printUsage<TargetStream: TextOutputStream>(_ error: Error, to: inout TargetStream) {
let format = formatOutput != nil ? formatOutput! : defaultFormat
print(format("\(error)", .error), terminator: "", to: &to)
printUsage(&to)
}
/**
* Prints a usage message.
*
* - parameter error: An error thrown from `parse()`. A description of the error
* (e.g. "Missing required option --extract") will be printed before the usage message.
*/
public func printUsage(_ error: Error) {
var out = StderrOutputStream.stream
printUsage(error, to: &out)
}
/**
* Prints a usage message.
*/
open func printUsage() {
var out = StderrOutputStream.stream
printUsage(&out)
}
}
/*
* Option.swift
* Copyright (c) 2014 Ben Gollmer.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The base class for a command-line option.
*/
open class Option {
open let shortFlag: String?
open let longFlag: String?
open let required: Bool
open let helpMessage: String
/** True if the option was set when parsing command-line arguments */
open var wasSet: Bool {
return false
}
open var claimedValues: Int { return 0 }
open var flagDescription: String {
switch (shortFlag, longFlag) {
case let (sf?, lf?):
return "\(ShortOptionPrefix)\(sf), \(LongOptionPrefix)\(lf)"
case (nil, let lf?):
return "\(LongOptionPrefix)\(lf)"
case (let sf?, nil):
return "\(ShortOptionPrefix)\(sf)"
default:
return ""
}
}
internal init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) {
if let sf = shortFlag {
assert(sf.count == 1, "Short flag must be a single character")
assert(Int(sf) == nil && Double(sf) == nil, "Short flag cannot be a numeric value")
}
if let lf = longFlag {
assert(Int(lf) == nil && Double(lf) == nil, "Long flag cannot be a numeric value")
}
self.shortFlag = shortFlag
self.longFlag = longFlag
self.helpMessage = helpMessage
self.required = required
}
/* The optional casts in these initalizers force them to call the private initializer. Without
* the casts, they recursively call themselves.
*/
/** Initializes a new Option that has both long and short flags. */
public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) {
self.init(shortFlag as String?, longFlag, required, helpMessage)
}
/** Initializes a new Option that has only a short flag. */
public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) {
self.init(shortFlag as String?, nil, required, helpMessage)
}
/** Initializes a new Option that has only a long flag. */
public convenience init(longFlag: String, required: Bool = false, helpMessage: String) {
self.init(nil, longFlag as String?, required, helpMessage)
}
func flagMatch(_ flag: String) -> Bool {
return flag == shortFlag || flag == longFlag
}
func setValue(_ values: [String]) -> Bool {
return false
}
}
/**
* A boolean option. The presence of either the short or long flag will set the value to true;
* absence of the flag(s) is equivalent to false.
*/
open class BoolOption: Option {
fileprivate var _value: Bool = false
open var value: Bool {
return _value
}
override open var wasSet: Bool {
return _value
}
override func setValue(_ values: [String]) -> Bool {
_value = true
return true
}
}
/** An option that accepts a positive or negative integer value. */
open class IntOption: Option {
fileprivate var _value: Int?
open var value: Int? {
return _value
}
override open var wasSet: Bool {
return _value != nil
}
override open var claimedValues: Int {
return _value != nil ? 1 : 0
}
override func setValue(_ values: [String]) -> Bool {
if values.count == 0 {
return false
}
if let val = Int(values[0]) {
_value = val
return true
}
return false
}
}
/**
* An option that represents an integer counter. Each time the short or long flag is found
* on the command-line, the counter will be incremented.
*/
open class CounterOption: Option {
fileprivate var _value: Int = 0
open var value: Int {
return _value
}
override open var wasSet: Bool {
return _value > 0
}
open func reset() {
_value = 0
}
override func setValue(_ values: [String]) -> Bool {
_value += 1
return true
}
}
/** An option that accepts a positive or negative floating-point value. */
open class DoubleOption: Option {
fileprivate var _value: Double?
open var value: Double? {
return _value
}
override open var wasSet: Bool {
return _value != nil
}
override open var claimedValues: Int {
return _value != nil ? 1 : 0
}
override func setValue(_ values: [String]) -> Bool {
if values.count == 0 {
return false
}
if let val = values[0].toDouble() {
_value = val
return true
}
return false
}
}
/** An option that accepts a string value. */
open class StringOption: Option {
fileprivate var _value: String? = nil
open var value: String? {
return _value
}
override open var wasSet: Bool {
return _value != nil
}
override open var claimedValues: Int {
return _value != nil ? 1 : 0
}
override func setValue(_ values: [String]) -> Bool {
if values.count == 0 {
return false
}
_value = values[0]
return true
}
}
/** An option that accepts one or more string values. */
open class MultiStringOption: Option {
fileprivate var _value: [String]?
open var value: [String]? {
return _value
}
override open var wasSet: Bool {
return _value != nil
}
override open var claimedValues: Int {
if let v = _value {
return v.count
}
return 0
}
override func setValue(_ values: [String]) -> Bool {
if values.count == 0 {
return false
}
_value = values
return true
}
}
/** An option that represents an enum value. */
public class EnumOption<T:RawRepresentable>: Option where T.RawValue == String {
private var _value: T?
public var value: T? {
return _value
}
override public var wasSet: Bool {
return _value != nil
}
override public var claimedValues: Int {
return _value != nil ? 1 : 0
}
/* Re-defining the intializers is necessary to make the Swift 2 compiler happy, as
* of Xcode 7 beta 2.
*/
internal override init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) {
super.init(shortFlag, longFlag, required, helpMessage)
}
/** Initializes a new Option that has both long and short flags. */
public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) {
self.init(shortFlag as String?, longFlag, required, helpMessage)
}
/** Initializes a new Option that has only a short flag. */
public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) {
self.init(shortFlag as String?, nil, required, helpMessage)
}
/** Initializes a new Option that has only a long flag. */
public convenience init(longFlag: String, required: Bool = false, helpMessage: String) {
self.init(nil, longFlag as String?, required, helpMessage)
}
override func setValue(_ values: [String]) -> Bool {
if values.count == 0 {
return false
}
if let v = T(rawValue: values[0]) {
_value = v
return true
}
return false
}
}
/*
* StringExtensions.swift
* Copyright (c) 2014 Ben Gollmer.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
internal extension String {
/* Retrieves locale-specified decimal separator from the environment
* using localeconv(3).
*/
fileprivate func _localDecimalPoint() -> Character {
let locale = localeconv()
if locale != nil {
if let decimalPoint = locale?.pointee.decimal_point {
return Character(UnicodeScalar(UInt32(decimalPoint.pointee))!)
}
}
return "."
}
/**
* Attempts to parse the string value into a Double.
*
* - returns: A Double if the string can be parsed, nil otherwise.
*/
func toDouble() -> Double? {
var characteristic: String = "0"
var mantissa: String = "0"
var inMantissa: Bool = false
var isNegative: Bool = false
let decimalPoint = self._localDecimalPoint()
#if swift(>=3.0)
let charactersEnumerator = self.enumerated()
#else
let charactersEnumerator = self.enumerated()
#endif
for (i, c) in charactersEnumerator {
if i == 0 && c == "-" {
isNegative = true
continue
}
if c == decimalPoint {
inMantissa = true
continue
}
if Int(String(c)) != nil {
if !inMantissa {
characteristic.append(c)
} else {
mantissa.append(c)
}
} else {
/* Non-numeric character found, bail */
return nil
}
}
let doubleCharacteristic = Double(Int(characteristic)!)
return (doubleCharacteristic +
Double(Int(mantissa)!) / pow(Double(10), Double(mantissa.count - 1))) *
(isNegative ? -1 : 1)
}
/**
* Splits a string into an array of string components.
*
* - parameter by: The character to split on.
* - parameter maxSplits: The maximum number of splits to perform. If 0, all possible splits are made.
*
* - returns: An array of string components.
*/
func split(by: Character, maxSplits: Int = 0) -> [String] {
var s = [String]()
var numSplits = 0
var curIdx = self.startIndex
for i in self.indices {
let c = self[i]
if c == by && (maxSplits == 0 || numSplits < maxSplits) {
let str = String(self[curIdx..<i])
s.append(str)
curIdx = self.index(after: i)
numSplits += 1
}
}
if curIdx != self.endIndex {
let str = String(self[curIdx..<self.endIndex])
s.append(str)
}
return s
}
/**
* Pads a string to the specified width.
*
* - parameter toWidth: The width to pad the string to.
* - parameter by: The character to use for padding.
*
* - returns: A new string, padded to the given width.
*/
func padded(toWidth width: Int, with padChar: Character = " ") -> String {
var s = self
var currentLength = self.count
while currentLength < width {
s.append(padChar)
currentLength += 1
}
return s
}
/**
* Wraps a string to the specified width.
*
* This just does simple greedy word-packing, it doesn't go full Knuth-Plass.
* If a single word is longer than the line width, it will be placed (unsplit)
* on a line by itself.
*
* - parameter atWidth: The maximum length of a line.
* - parameter wrapBy: The line break character to use.
* - parameter splitBy: The character to use when splitting the string into words.
*
* - returns: A new string, wrapped at the given width.
*/
func wrapped(atWidth width: Int, wrapBy: Character = "\n", splitBy: Character = " ") -> String {
var s = ""
var currentLineWidth = 0
for word in self.split(by: splitBy) {
let wordLength = word.count
if currentLineWidth + wordLength + 1 > width {
/* Word length is greater than line length, can't wrap */
if wordLength >= width {
s += word
}
s.append(wrapBy)
currentLineWidth = 0
}
currentLineWidth += wordLength + 1
s += word
s.append(splitBy)
}
return s
}
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
//MARK: - Import
import Foundation
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
//MARK: - Definitions
private var BASE_CLASS_NAME : String = "Localizations"
private let OBJC_CLASS_PREFIX : String = "_"
private var OBJC_CUSTOM_SUPERCLASS: String?
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Extensions
private extension String {
func alphanumericString(exceptionCharactersFromString: String = "") -> String {
// removes diacritic marks
var copy = self.folding(options: .diacriticInsensitive, locale: NSLocale.current)
// removes all non alphanumeric characters
var characterSet = CharacterSet.alphanumerics.inverted
// don't remove the characters that are given
characterSet.remove(charactersIn: exceptionCharactersFromString)
copy = copy.components(separatedBy: characterSet).reduce("") { $0 + $1 }
return copy
}
func replacedNonAlphaNumericCharacters(replacement: UnicodeScalar) -> String {
return String(describing: UnicodeScalarView(self.unicodeScalars.map { CharacterSet.alphanumerics.contains(($0)) ? $0 : replacement }))
}
}
private extension NSCharacterSet {
// thanks to http://stackoverflow.com/a/27698155/354018
func containsCharacter(c: Character) -> Bool {
let s = String(c)
let ix = s.startIndex
let ix2 = s.endIndex
let result = s.rangeOfCharacter(from: self as CharacterSet, options: [], range: ix..<ix2)
return result != nil
}
}
private extension NSMutableDictionary {
func setObject(object : AnyObject!, forKeyPath : String, delimiter : String = ".") {
self.setObject(object: object, onObject : self, forKeyPath: forKeyPath, createIntermediates: true, replaceIntermediates: true, delimiter: delimiter)
}
func setObject(object : AnyObject, onObject : AnyObject, forKeyPath keyPath : String, createIntermediates: Bool, replaceIntermediates: Bool, delimiter: String) {
// Make keypath mutable
var primaryKeypath = keyPath
// Replace delimiter with dot delimiter - otherwise key value observing does not work properly
let baseDelimiter = "."
primaryKeypath = primaryKeypath.replacingOccurrences(of: delimiter, with: baseDelimiter, options: NSString.CompareOptions.literal, range: nil)
// Create path components separated by delimiter (. by default) and get key for root object
// filter empty path components, these can be caused by delimiter at beginning/end, or multiple consecutive delimiters in the middle
let pathComponents : Array<String> = primaryKeypath.components(separatedBy: baseDelimiter).filter({ $0.count > 0 })
primaryKeypath = pathComponents.joined(separator: baseDelimiter)
let rootKey : String = pathComponents[0]
if pathComponents.count == 1 {
onObject.set(object, forKey: rootKey)
}
let replacementDictionary : NSMutableDictionary = NSMutableDictionary()
// Store current state for further replacement
var previousObject : AnyObject? = onObject;
var previousReplacement : NSMutableDictionary = replacementDictionary
var reachedDictionaryLeaf : Bool = false;
// Traverse through path from root to deepest level
for path : String in pathComponents {
let currentObject : AnyObject? = reachedDictionaryLeaf ? nil : previousObject?.object(forKey: path) as AnyObject?
// Check if object already exists. If not, create new level, if allowed, or end
if currentObject == nil {
reachedDictionaryLeaf = true;
if createIntermediates {
let newNode : NSMutableDictionary = NSMutableDictionary()
previousReplacement.setObject(newNode, forKey: path as NSString)
previousReplacement = newNode;
} else {
return;
}
// If it does and it is dictionary, create mutable copy and assign new node there
} else if currentObject is NSDictionary {
let newNode : NSMutableDictionary = NSMutableDictionary(dictionary: currentObject as! [NSObject : AnyObject])
previousReplacement.setObject(newNode, forKey: path as NSString)
previousReplacement = newNode
// It exists but it is not NSDictionary, so we replace it, if allowed, or end
} else {
reachedDictionaryLeaf = true;
if replaceIntermediates {
let newNode : NSMutableDictionary = NSMutableDictionary()
previousReplacement.setObject(newNode, forKey: path as NSString)
previousReplacement = newNode;
} else {
return;
}
}
// Replace previous object with the new one
previousObject = currentObject;
}
// Replace root object with newly created n-level dictionary
replacementDictionary.setValue(object, forKeyPath: primaryKeypath);
onObject.set(replacementDictionary.object(forKey: rootKey), forKey: rootKey);
}
}
private extension FileManager {
func isDirectoryAtPath(path : String) -> Bool {
let manager = FileManager.default
do {
let attribs: [FileAttributeKey : Any]? = try manager.attributesOfItem(atPath: path)
if let attributes = attribs {
let type = attributes[FileAttributeKey.type] as? String
return type == FileAttributeType.typeDirectory.rawValue
}
} catch _ {
return false
}
}
}
private extension String {
var camelCasedString: String {
let inputArray = self.components(separatedBy: (CharacterSet.alphanumerics.inverted))
return inputArray.reduce("", {$0 + $1.capitalized})
}
var nolineString: String {
let set = CharacterSet.newlines
let components = self.components(separatedBy: set)
return components.joined(separator: " ")
}
func isFirstLetterDigit() -> Bool {
guard let c : Character = self.first else {
return false
}
let s = String(c).unicodeScalars
let uni = s[s.startIndex]
return (uni.value >= 48 && uni.value <= 57)
// return String(describing: UnicodeScalarView(self.unicodeScalars.map { CharacterSet.alphanumerics.contains(($0)) ? $0 : replacement }))
}
func isReservedKeyword(lang : Runtime.ExportLanguage) -> Bool {
// Define keywords for each language
var keywords : [String] = []
if lang == .ObjC {
keywords = ["auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long",
"register", "restrict", "return", "short", "signed", "sizeof", "static", "struct", "swift", "typedef", "union", "unsigned", "void", "volatile", "while",
"BOOL", "Class", "bycopy", "byref", "id", "IMP", "in", "inout", "nil", "NO", "NULL", "oneway", "out", "Protocol", "SEL", "self", "super", "YES"]
} else if lang == .Swift {
keywords = ["class", "deinit", "enum", "extension", "func", "import", "init", "inout", "internal", "let", "operator", "private", "protocol", "public", "static", "struct", "subscript", "typealias", "var", "break", "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch", "where", "while", "as", "catch", "dynamicType", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try", "type", "__COLUMN__", "__FILE__", "__FUNCTION__", "__LINE__"]
}
// Check if it contains that keyword
return keywords.index(of: self) != nil
}
}
private enum SpecialCharacter {
case String
case Double
case Int
case Int64
case UInt
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Localization Class implementation
class Localization {
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Properties
var flatStructure = NSDictionary()
var objectStructure = NSMutableDictionary()
var autocapitalize : Bool = true
var table: String?
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Setup
convenience init(inputFile : URL, delimiter : String, autocapitalize : Bool, table: String? = nil) {
self.init()
self.table = table
// Load localization file
self.processInputFromFile(file: inputFile, delimiter: delimiter, autocapitalize: autocapitalize)
}
func processInputFromFile(file : URL, delimiter : String, autocapitalize : Bool) {
guard let dictionary = NSDictionary(contentsOfFile: file.path) else {
// TODO: Better error handling
print("Bad format of input file")
exit(EX_IOERR)
}
self.flatStructure = dictionary
self.autocapitalize = autocapitalize
self.expandFlatStructure(flatStructure: dictionary, delimiter: delimiter)
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Public
func writerWithSwiftImplementation() -> StreamWriter {
let writer = StreamWriter()
// Generate header
writer.writeHeader()
// Imports
writer.writeMarkWithName(name: "Imports")
writer.writeSwiftImports()
// Generate actual localization structures
writer.writeMarkWithName(name: "Localizations")
writer.writeCodeStructure(structure: self.swiftStructWithContent(content: self.codifySwift(expandedStructure: self.objectStructure), structName: BASE_CLASS_NAME, contentLevel: 0))
return writer
}
func writerWithObjCImplementationWithFilename(filename : String) -> StreamWriter {
let writer = StreamWriter()
// Generate header
writer.writeHeader()
// Imports
writer.writeMarkWithName(name: "Imports")
writer.writeObjCImportsWithFileName(name: filename)
// Generate actual localization structures
writer.writeMarkWithName(name: "Header")
writer.writeCodeStructure(structure: self.codifyObjC(expandedStructure: self.objectStructure, baseClass: BASE_CLASS_NAME, header: false))
return writer
}
func writerWithObjCHeader() -> StreamWriter {
let writer = StreamWriter()
// Generate header
writer.writeHeader()
// Imports
writer.writeMarkWithName(name: "Imports")
writer.writeObjCHeaderImports()
// Generate actual localization structures
writer.writeMarkWithName(name: "Header")
writer.writeCodeStructure(structure: self.codifyObjC(expandedStructure: self.objectStructure, baseClass: BASE_CLASS_NAME, header: true))
// Generate macros
writer.writeMarkWithName(name: "Macros")
writer.writeObjCHeaderMacros()
return writer
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Private
private func expandFlatStructure(flatStructure : NSDictionary, delimiter: String) {
// Writes values to dictionary and also
for (key, _) in flatStructure {
guard let key = key as? String else { continue }
objectStructure.setObject(object: key as NSString, forKeyPath: key, delimiter: delimiter)
}
}
private func codifySwift(expandedStructure : NSDictionary, contentLevel : Int = 0) -> String {
// Increase content level
let contentLevel = contentLevel + 1
// Prepare output structure
var outputStructure : [String] = []
// First iterate through properties
for (key, value) in expandedStructure {
if let value = value as? String {
let comment = (self.flatStructure.object(forKey: value) as! String).nolineString
let methodParams = self.methodParamsForString(string: comment)
let staticString: String
if methodParams.count > 0 {
staticString = self.swiftLocalizationFuncFromLocalizationKey(key: value, methodName: key as! String, baseTranslation: comment, methodSpecification: methodParams, contentLevel: contentLevel)
} else {
staticString = self.swiftLocalizationStaticVarFromLocalizationKey(key: value, variableName: key as! String, baseTranslation: comment, contentLevel: contentLevel)
}
outputStructure.append(staticString)
}
}
// Then iterate through nested structures
for (key, value) in expandedStructure {
if let value = value as? NSDictionary {
outputStructure.append(self.swiftStructWithContent(content: self.codifySwift(expandedStructure: value, contentLevel: contentLevel), structName: key as! String, contentLevel: contentLevel))
}
}
// At the end, return everything merged together
return outputStructure.joined(separator: "\n")
}
private func codifyObjC(expandedStructure : NSDictionary, baseClass : String, header : Bool) -> String {
// Prepare output structure
var outputStructure : [String] = []
var contentStructure : [String] = []
// First iterate through properties
for (key, value) in expandedStructure {
if let value = value as? String {
let comment = (self.flatStructure.object(forKey: value) as! String).nolineString
let methodParams = self.methodParamsForString(string: comment)
let staticString : String
if methodParams.count > 0 {
staticString = self.objcLocalizationFuncFromLocalizationKey(key: value, methodName: self.variableName(string: key as! String, lang: .ObjC), baseTranslation: comment, methodSpecification: methodParams, header: header)
} else {
staticString = self.objcLocalizationStaticVarFromLocalizationKey(key: value, variableName: self.variableName(string: key as! String, lang: .ObjC), baseTranslation: comment, header: header)
}
contentStructure.append(staticString)
}
}
// Then iterate through nested structures
for (key, value) in expandedStructure {
if let value = value as? NSDictionary {
outputStructure.append(self.codifyObjC(expandedStructure: value, baseClass : baseClass + self.variableName(string: key as! String, lang: .ObjC), header: header))
contentStructure.insert(self.objcClassVarWithName(name: self.variableName(string: key as! String, lang: .ObjC), className: baseClass + self.variableName(string: key as! String, lang: .ObjC), header: header), at: 0)
}
}
if baseClass == BASE_CLASS_NAME {
if header {
contentStructure.append(TemplateFactory.templateForObjCBaseClassHeader(name: OBJC_CLASS_PREFIX + BASE_CLASS_NAME))
} else {
contentStructure.append(TemplateFactory.templateForObjCBaseClassImplementation(name: OBJC_CLASS_PREFIX + BASE_CLASS_NAME))
}
}
// Generate class code for current class
outputStructure.append(self.objcClassWithContent(content: contentStructure.joined(separator: "\n"), className: OBJC_CLASS_PREFIX + baseClass, header: header))
// At the end, return everything merged together
return outputStructure.joined(separator: "\n")
}
private func methodParamsForString(string : String) -> [SpecialCharacter] {
// Split the string into pieces by %
let matches = self.matchesForRegexInText(regex: "%([0-9]*.[0-9]*(d|i|u|f|ld)|(\\d\\$)?@|d|i|u|f|ld)", text: string)
var characters : [SpecialCharacter] = []
for match in matches {
characters.append(self.propertyTypeForMatch(string: match))
}
return characters
}
private func propertyTypeForMatch(string : String) -> SpecialCharacter {
if string.contains("ld") {
return .Int64
} else if string.contains("d") || string.contains("i") {
return .Int
} else if string.contains("u") {
return .UInt
} else if string.contains("f") {
return .Double
} else {
return .String
}
}
private func variableName(string : String, lang : Runtime.ExportLanguage) -> String {
// . is not allowed, nested structure expanding must take place before calling this function
let legalCharacterString = string.replacedNonAlphaNumericCharacters(replacement: "_")
if self.autocapitalize {
return (legalCharacterString.isFirstLetterDigit() || legalCharacterString.isReservedKeyword(lang: lang) ? "_" + legalCharacterString.camelCasedString : legalCharacterString.camelCasedString)
} else {
return (legalCharacterString.isFirstLetterDigit() || legalCharacterString.isReservedKeyword(lang: lang) ? "_" + string : legalCharacterString)
}
}
private func matchesForRegexInText(regex: String!, text: String!) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matches(in: text, options: [], range: NSMakeRange(0, nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
private func dataTypeFromSpecialCharacter(char : SpecialCharacter, language : Runtime.ExportLanguage) -> String {
switch char {
case .String: return language == .Swift ? "String" : "NSString *"
case .Double: return language == .Swift ? "Double" : "double"
case .Int: return language == .Swift ? "Int" : "int"
case .Int64: return language == .Swift ? "Int64" : "long"
case .UInt: return language == .Swift ? "UInt" : "unsigned int"
}
}
private func swiftStructWithContent(content : String, structName : String, contentLevel : Int = 0) -> String {
return TemplateFactory.templateForSwiftStructWithName(name: self.variableName(string: structName, lang: .Swift), content: content, contentLevel: contentLevel)
}
private func swiftLocalizationStaticVarFromLocalizationKey(key : String, variableName : String, baseTranslation : String, contentLevel : Int = 0) -> String {
return TemplateFactory.templateForSwiftStaticVarWithName(name: self.variableName(string: variableName, lang: .Swift), key: key, table: table, baseTranslation : baseTranslation, contentLevel: contentLevel)
}
private func swiftLocalizationFuncFromLocalizationKey(key : String, methodName : String, baseTranslation : String, methodSpecification : [SpecialCharacter], contentLevel : Int = 0) -> String {
var counter = 0
var methodHeaderParams = methodSpecification.reduce("") { (string, character) -> String in
counter += 1
return "\(string), _ value\(counter) : \(self.dataTypeFromSpecialCharacter(char: character, language: .Swift))"
}
var methodParams : [String] = []
for (index, _) in methodSpecification.enumerated() {
methodParams.append("value\(index + 1)")
}
let methodParamsString = methodParams.joined(separator: ", ")
methodHeaderParams = methodHeaderParams.trimmingCharacters(in: CharacterSet(charactersIn: ", "))
return TemplateFactory.templateForSwiftFuncWithName(name: self.variableName(string: methodName, lang: .Swift), key: key, table: table, baseTranslation : baseTranslation, methodHeader: methodHeaderParams, params: methodParamsString, contentLevel: contentLevel)
}
private func objcClassWithContent(content : String, className : String, header : Bool, contentLevel : Int = 0) -> String {
if header {
return TemplateFactory.templateForObjCClassHeaderWithName(name: className, content: content, contentLevel: contentLevel)
} else {
return TemplateFactory.templateForObjCClassImplementationWithName(name: className, content: content, contentLevel: contentLevel)
}
}
private func objcClassVarWithName(name : String, className : String, header : Bool, contentLevel : Int = 0) -> String {
if header {
return TemplateFactory.templateForObjCClassVarHeaderWithName(name: name, className: className, contentLevel: contentLevel)
} else {
return TemplateFactory.templateForObjCClassVarImplementationWithName(name: name, className: className, contentLevel: contentLevel)
}
}
private func objcLocalizationStaticVarFromLocalizationKey(key : String, variableName : String, baseTranslation : String, header : Bool, contentLevel : Int = 0) -> String {
if header {
return TemplateFactory.templateForObjCStaticVarHeaderWithName(name: variableName, key: key, baseTranslation : baseTranslation, contentLevel: contentLevel)
} else {
return TemplateFactory.templateForObjCStaticVarImplementationWithName(name: variableName, key: key, table: table, baseTranslation : baseTranslation, contentLevel: contentLevel)
}
}
private func objcLocalizationFuncFromLocalizationKey(key : String, methodName : String, baseTranslation : String, methodSpecification : [SpecialCharacter], header : Bool, contentLevel : Int = 0) -> String {
var counter = 0
var methodHeader = methodSpecification.reduce("") { (string, character) -> String in
counter += 1
return "\(string), \(self.dataTypeFromSpecialCharacter(char: character, language: .ObjC))"
}
counter = 0
var blockHeader = methodSpecification.reduce("") { (string, character) -> String in
counter += 1
return "\(string), \(self.dataTypeFromSpecialCharacter(char: character, language: .ObjC)) value\(counter) "
}
var blockParamComponent : [String] = []
for (index, _) in methodSpecification.enumerated() {
blockParamComponent.append("value\(index + 1)")
}
let blockParams = blockParamComponent.joined(separator: ", ")
methodHeader = methodHeader.trimmingCharacters(in: CharacterSet(charactersIn: ", "))
blockHeader = blockHeader.trimmingCharacters(in: CharacterSet(charactersIn: ", "))
if header {
return TemplateFactory.templateForObjCMethodHeaderWithName(name: methodName, key: key, baseTranslation: baseTranslation, methodHeader: methodHeader, contentLevel: contentLevel)
} else {
return TemplateFactory.templateForObjCMethodImplementationWithName(name: methodName, key: key, table: table, baseTranslation: baseTranslation, methodHeader: methodHeader, blockHeader: blockHeader, blockParams: blockParams, contentLevel: contentLevel)
}
}
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Runtime Class implementation
class Runtime {
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Properties
enum ExportLanguage : String {
case Swift = "swift"
case ObjC = "objc"
}
enum ExportStream : String {
case Standard = "stdout"
case File = "file"
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Properties
var localizationFilePathToRead : URL!
var localizationFilePathToWriteTo : URL!
var localizationFileHeaderPathToWriteTo : URL?
var localizationDelimiter = "."
var localizationDebug = false
var localizationCore : Localization!
var localizationExportLanguage : ExportLanguage = .Swift
var localizationExportStream : ExportStream = .File
var localizationAutocapitalize = false
var localizationStringsTable: String?
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Public
func run() {
// Initialize command line tool
if self.checkCLI() {
// Process files
if self.checkIO() {
// Generate input -> output based on user configuration
self.processOutput()
}
}
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Private
private func checkCLI() -> Bool {
// Define CLI options
let inputFilePath = StringOption(shortFlag: "i", longFlag: "input", required: true,
helpMessage: "Required | String | Path to the localization file")
let outputFilePath = StringOption(shortFlag: "o", longFlag: "output", required: false,
helpMessage: "Optional | String | Path to output file (.swift or .m, depending on your configuration. If you are using ObjC, header will be created on that location. If ommited, output will be sent to stdout instead.")
let outputLanguage = StringOption(shortFlag: "l", longFlag: "language", required: false,
helpMessage: "Optional | String | [swift | objc] | Specifies language of generated output files | Defaults to [swift]")
let delimiter = StringOption(shortFlag: "d", longFlag: "delimiter", required: false,
helpMessage: "Optional | String | String delimiter to separate segments of each string | Defaults to [.]")
let autocapitalize = BoolOption(shortFlag: "c", longFlag: "capitalize", required: false,
helpMessage: "Optional | Bool | When enabled, name of all structures / methods / properties are automatically CamelCased | Defaults to false")
let baseClassName = StringOption(shortFlag: "b", longFlag: "baseClassName", required: false,
helpMessage: "Optional | String | Name of the base class | Defaults to \"Localizations\"")
let stringsTableName = StringOption(shortFlag: "t", longFlag: "stringsTableName", required: false,
helpMessage: "Optional | String | Name of strings table | Defaults to nil")
let customSuperclass = StringOption(shortFlag: "s", longFlag: "customSuperclass", required: false,
helpMessage: "Optional | String | A custom superclass name | Defaults to NSObject (only applicable in ObjC)")
let cli = CommandLine()
cli.addOptions(inputFilePath, outputFilePath, outputLanguage, delimiter, autocapitalize, baseClassName, stringsTableName, customSuperclass)
// TODO: make output file path NOT optional when print output stream is selected
do {
// Parse user input
try cli.parse(strict: true)
// It passed, now process input
self.localizationFilePathToRead = URL(fileURLWithPath: inputFilePath.value!)
if let value = delimiter.value { self.localizationDelimiter = value }
self.localizationAutocapitalize = autocapitalize.wasSet ? true : false
if let value = outputLanguage.value, let type = ExportLanguage(rawValue: value) { self.localizationExportLanguage = type }
if let value = outputFilePath.value {
self.localizationFilePathToWriteTo = URL(fileURLWithPath: value)
self.localizationExportStream = .File
} else {
self.localizationExportStream = .Standard
}
if let bcn = baseClassName.value {
BASE_CLASS_NAME = bcn
}
self.localizationStringsTable = stringsTableName.value
OBJC_CUSTOM_SUPERCLASS = customSuperclass.value
return true
} catch {
cli.printUsage(error)
exit(EX_USAGE)
}
return false
}
private func checkIO() -> Bool {
// Check if we have input file
if !FileManager.default.fileExists(atPath: self.localizationFilePathToRead.path) {
// TODO: Better error handling
exit(EX_IOERR)
}
// Handle output file checks only if we are writing to file
if self.localizationExportStream == .File {
// Remove output file first
_ = try? FileManager.default.removeItem(atPath: self.localizationFilePathToWriteTo.path)
if !FileManager.default.createFile(atPath: self.localizationFilePathToWriteTo.path, contents: Data(), attributes: nil) {
// TODO: Better error handling
exit(EX_IOERR)
}
// ObjC - we also need header file for ObjC code
if self.localizationExportLanguage == .ObjC {
// Create header file name
self.localizationFileHeaderPathToWriteTo = self.localizationFilePathToWriteTo.deletingPathExtension().appendingPathExtension("h")
// Remove file at path and replace it with new one
_ = try? FileManager.default.removeItem(atPath: self.localizationFileHeaderPathToWriteTo!.path)
if !FileManager.default.createFile(atPath: self.localizationFileHeaderPathToWriteTo!.path, contents: Data(), attributes: nil) {
// TODO: Better error handling
exit(EX_IOERR)
}
}
}
return true
}
private func processOutput() {
// Create translation core which will process all required data
self.localizationCore = Localization(inputFile: self.localizationFilePathToRead, delimiter: self.localizationDelimiter, autocapitalize: self.localizationAutocapitalize, table: self.localizationStringsTable)
// Write output for swift
if self.localizationExportLanguage == .Swift {
let implementation = self.localizationCore.writerWithSwiftImplementation()
// Write swift file
if self.localizationExportStream == .Standard {
implementation.writeToSTD(clearBuffer: true)
} else if self.localizationExportStream == .File {
implementation.writeToOutputFileAtPath(path: self.localizationFilePathToWriteTo)
}
// or write output for objc, based on user configuration
} else if self.localizationExportLanguage == .ObjC {
let implementation = self.localizationCore.writerWithObjCImplementationWithFilename(filename: self.localizationFilePathToWriteTo.deletingPathExtension().lastPathComponent)
let header = self.localizationCore.writerWithObjCHeader()
// Write .h and .m file
if self.localizationExportStream == .Standard {
header.writeToSTD(clearBuffer: true)
implementation.writeToSTD(clearBuffer: true)
} else if self.localizationExportStream == .File {
implementation.writeToOutputFileAtPath(path: self.localizationFilePathToWriteTo)
header.writeToOutputFileAtPath(path: self.localizationFileHeaderPathToWriteTo!)
}
}
}
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - LocalizationPrinter Class implementation
class StreamWriter {
var outputBuffer : String = ""
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Public
func writeHeader() {
// let formatter = NSDateFormatter()
// formatter.dateFormat = "yyyy-MM-dd 'at' h:mm a"
self.store(string: "//\n")
self.store(string: "// Autogenerated by Satish Babariya \n")
self.store(string: "// Do not change this file manually!\n")
self.store(string: "//\n")
// self.store("// \(formatter.stringFromDate(NSDate()))\n")
// self.store("//\n")
}
func writeMarkWithName(name : String, contentLevel : Int = 0) {
self.store(string: TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "\n")
self.store(string: TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "\n")
self.store(string: TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n")
self.store(string: TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "// MARK: - \(name)\n")
self.store(string: TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "\n")
}
func writeSwiftImports() {
self.store(string: "import Foundation\n")
}
func writeObjCImportsWithFileName(name : String) {
self.store(string: "#import \"\(name).h\"\n")
}
func writeObjCHeaderImports() {
self.store(string: "@import Foundation;\n")
if let csc = OBJC_CUSTOM_SUPERCLASS {
self.store(string: "#import \"\(csc).h\"\n")
}
}
func writeObjCHeaderMacros() {
self.store(string: "// Make localization to be easily accessible\n")
self.store(string: "#define \(BASE_CLASS_NAME) [\(OBJC_CLASS_PREFIX)\(BASE_CLASS_NAME) sharedInstance]\n")
}
func writeCodeStructure(structure : String) {
self.store(string: structure)
}
func writeToOutputFileAtPath(path : URL, clearBuffer : Bool = true) {
_ = try? self.outputBuffer.write(toFile: path.path, atomically: true, encoding: String.Encoding.utf8)
if clearBuffer {
self.outputBuffer = ""
}
}
func writeToSTD(clearBuffer : Bool = true) {
print(self.outputBuffer)
if clearBuffer {
self.outputBuffer = ""
}
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Private
private func store(string : String) {
self.outputBuffer += string
}
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - TemplateFactory Class implementation
class TemplateFactory {
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Public - Swift Templates
class func templateForSwiftStructWithName(name : String, content : String, contentLevel : Int) -> String {
return "\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "public struct \(name) {\n"
+ "\n"
+ "\(content)\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "}"
}
class func templateForSwiftStaticVarWithName(name : String, key : String, table: String?, baseTranslation : String, contentLevel : Int) -> String {
let tableName: String
if let table = table {
tableName = "tableName: \"\(table)\", "
} else {
tableName = ""
}
return TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "/// Base translation: \(baseTranslation)\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "public static var \(name) : String = NSLocalizedString(\"\(key)\", \(tableName)comment: \"\")\n"
}
class func templateForSwiftFuncWithName(name : String, key : String, table: String?, baseTranslation : String, methodHeader : String, params : String, contentLevel : Int) -> String {
let tableName: String
if let table = table {
tableName = "tableName: \"\(table)\", "
} else {
tableName = ""
}
return TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "/// Base translation: \(baseTranslation)\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "public static func \(name)(\(methodHeader)) -> String {\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: contentLevel + 1) + "return String(format: NSLocalizedString(\"\(key)\", \(tableName)comment: \"\"), \(params))\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: contentLevel) + "}\n"
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Public - ObjC templates
class func templateForObjCClassImplementationWithName(name : String, content : String, contentLevel : Int) -> String {
return "@implementation \(name)\n\n"
+ "\(content)\n"
+ "@end\n\n"
}
class func templateForObjCStaticVarImplementationWithName(name : String, key : String, table: String?, baseTranslation : String, contentLevel : Int) -> String {
let tableName = table != nil ? "@\"\(table!)\"" : "nil"
return "- (NSString *)\(name) {\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 1) + "return NSLocalizedStringFromTable(@\"\(key)\", \(tableName), nil);\n"
+ "}\n"
}
class func templateForObjCClassVarImplementationWithName(name : String, className : String, contentLevel : Int) -> String {
return "- (_\(className) *)\(name) {\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 1) + "return [\(OBJC_CLASS_PREFIX)\(className) new];\n"
+ "}\n"
}
class func templateForObjCMethodImplementationWithName(name : String, key : String, table: String?, baseTranslation : String, methodHeader : String, blockHeader : String, blockParams : String, contentLevel : Int) -> String {
let tableName = table != nil ? "@\"\(table!)\"" : "nil"
return "- (NSString *(^)(\(methodHeader)))\(name) {\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 1) + "return ^(\(blockHeader)) {\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 2) + "return [NSString stringWithFormat: NSLocalizedStringFromTable(@\"\(key)\", \(tableName), nil), \(blockParams)];\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 1) + "};\n"
+ "}\n"
}
class func templateForObjCClassHeaderWithName(name : String, content : String, contentLevel : Int) -> String {
return "@interface \(name) : \(OBJC_CUSTOM_SUPERCLASS ?? "NSObject")\n\n"
+ "\(content)\n"
+ "@end\n"
}
class func templateForObjCStaticVarHeaderWithName(name : String, key : String, baseTranslation : String, contentLevel : Int) -> String {
return "/// Base translation: \(baseTranslation)\n"
+ "- (NSString *)\(name);\n"
}
class func templateForObjCClassVarHeaderWithName(name : String, className : String, contentLevel : Int) -> String {
return "- (_\(className) *)\(name);\n"
}
class func templateForObjCMethodHeaderWithName(name : String, key : String, baseTranslation : String, methodHeader : String, contentLevel : Int) -> String {
return "/// Base translation: \(baseTranslation)\n"
+ "- (NSString *(^)(\(methodHeader)))\(name);"
}
class func templateForObjCBaseClassHeader(name: String) -> String {
return "+ (\(name) *)sharedInstance;\n"
}
class func templateForObjCBaseClassImplementation(name : String) -> String {
return "+ (\(name) *)sharedInstance {\n"
+ "\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 1) + "static dispatch_once_t once;\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 1) + "static \(name) *instance;\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 1) + "dispatch_once(&once, ^{\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 2) + "instance = [[\(name) alloc] init];\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 1) + "});\n"
+ TemplateFactory.contentIndentForLevel(contentLevel: 1) + "return instance;\n"
+ "}"
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Public - Helpers
class func contentIndentForLevel(contentLevel : Int) -> String {
var outputString = ""
for _ in 0 ..< contentLevel {
outputString += " "
}
return outputString
}
}
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
// MARK: - Actual processing
let runtime = Runtime()
runtime.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment