init project
This commit is contained in:
109
.gitignore
vendored
Normal file
109
.gitignore
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
### Swift template
|
||||
# Xcode
|
||||
#
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData/
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
## Other
|
||||
*.moved-aside
|
||||
*.xccheckout
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
# Packages/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build
|
||||
|
||||
# Accio dependency management
|
||||
Dependencies/
|
||||
.accio/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
||||
|
||||
# Code Injection
|
||||
#
|
||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||
# https://github.com/johnno1962/injectionforxcode
|
||||
|
||||
iOSInjectionProject/
|
||||
|
||||
### Xcode template
|
||||
# Xcode
|
||||
#
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
## Xcode Patch
|
||||
*.xcodeproj/*
|
||||
!*.xcodeproj/project.pbxproj
|
||||
!*.xcodeproj/xcshareddata/
|
||||
!*.xcworkspace/contents.xcworkspacedata
|
||||
/*.gcno
|
||||
|
||||
*.swiftmodule/
|
||||
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# StringGenerator
|
||||
StringGenerator for Swift projects. Simple generation/add/converting localization files with enum.
|
||||
|
||||
## Requirements
|
||||
- Swift 5
|
||||
- Xcode 10.2
|
||||
|
||||
## How to use?
|
||||
Build this and run in terminal: ``stringen [command] [params...]`` or simple `stringen` for get help.
|
||||
326
StringGenerator.xcodeproj/project.pbxproj
Normal file
326
StringGenerator.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,326 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
6511B41622CE162A007B5B23 /* BaseCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6511B41522CE162A007B5B23 /* BaseCommand.swift */; };
|
||||
6511B41A22CE18CB007B5B23 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6511B41922CE18CB007B5B23 /* Colors.swift */; };
|
||||
6511B41C22CE1ADD007B5B23 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6511B41B22CE1ADD007B5B23 /* Helpers.swift */; };
|
||||
6511B41E22CE1B42007B5B23 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6511B41D22CE1B42007B5B23 /* Constants.swift */; };
|
||||
65177E0822D85C9300D125EF /* AddStringCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65177E0722D85C9300D125EF /* AddStringCommand.swift */; };
|
||||
A10CF0AF22339D820068E490 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10CF0AE22339D820068E490 /* main.swift */; };
|
||||
C05FE96C63084AB3DF0DDFDB /* GenerateCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05FEAF5984674C7F1F657AA /* GenerateCommand.swift */; };
|
||||
C05FEDB04CADDE3D52A8CF7D /* ConvertCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05FEB7E25F7F2601DE67B18 /* ConvertCommand.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
A10CF0A922339D820068E490 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
6511B41522CE162A007B5B23 /* BaseCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCommand.swift; sourceTree = "<group>"; };
|
||||
6511B41922CE18CB007B5B23 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
||||
6511B41B22CE1ADD007B5B23 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
||||
6511B41D22CE1B42007B5B23 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
65177E0722D85C9300D125EF /* AddStringCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddStringCommand.swift; sourceTree = "<group>"; };
|
||||
A10CF0AB22339D820068E490 /* stringen */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = stringen; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A10CF0AE22339D820068E490 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
C05FEAF5984674C7F1F657AA /* GenerateCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateCommand.swift; sourceTree = "<group>"; };
|
||||
C05FEB7E25F7F2601DE67B18 /* ConvertCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvertCommand.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
A10CF0A822339D820068E490 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
6511B41422CE1612007B5B23 /* Commands */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65177E0722D85C9300D125EF /* AddStringCommand.swift */,
|
||||
6511B41522CE162A007B5B23 /* BaseCommand.swift */,
|
||||
C05FEAF5984674C7F1F657AA /* GenerateCommand.swift */,
|
||||
C05FEB7E25F7F2601DE67B18 /* ConvertCommand.swift */,
|
||||
);
|
||||
path = Commands;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A10CF0A222339D820068E490 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A10CF0AD22339D820068E490 /* StringGenerator */,
|
||||
A10CF0AC22339D820068E490 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A10CF0AC22339D820068E490 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A10CF0AB22339D820068E490 /* stringen */,
|
||||
);
|
||||
path = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A10CF0AD22339D820068E490 /* StringGenerator */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6511B41422CE1612007B5B23 /* Commands */,
|
||||
A10CF0AE22339D820068E490 /* main.swift */,
|
||||
6511B41922CE18CB007B5B23 /* Colors.swift */,
|
||||
6511B41B22CE1ADD007B5B23 /* Helpers.swift */,
|
||||
6511B41D22CE1B42007B5B23 /* Constants.swift */,
|
||||
);
|
||||
path = StringGenerator;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
A10CF0AA22339D820068E490 /* StringGenerator */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = A10CF0B222339D820068E490 /* Build configuration list for PBXNativeTarget "StringGenerator" */;
|
||||
buildPhases = (
|
||||
A10CF0A722339D820068E490 /* Sources */,
|
||||
A10CF0A822339D820068E490 /* Frameworks */,
|
||||
A10CF0A922339D820068E490 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = StringGenerator;
|
||||
productName = StringGenerator;
|
||||
productReference = A10CF0AB22339D820068E490 /* stringen */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
A10CF0A322339D820068E490 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1010;
|
||||
LastUpgradeCheck = 1010;
|
||||
ORGANIZATIONNAME = Waveaccess;
|
||||
TargetAttributes = {
|
||||
A10CF0AA22339D820068E490 = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
LastSwiftMigration = 1020;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = A10CF0A622339D820068E490 /* Build configuration list for PBXProject "StringGenerator" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = A10CF0A222339D820068E490;
|
||||
productRefGroup = A10CF0AC22339D820068E490 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
A10CF0AA22339D820068E490 /* StringGenerator */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
A10CF0A722339D820068E490 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
65177E0822D85C9300D125EF /* AddStringCommand.swift in Sources */,
|
||||
6511B41622CE162A007B5B23 /* BaseCommand.swift in Sources */,
|
||||
6511B41A22CE18CB007B5B23 /* Colors.swift in Sources */,
|
||||
A10CF0AF22339D820068E490 /* main.swift in Sources */,
|
||||
6511B41C22CE1ADD007B5B23 /* Helpers.swift in Sources */,
|
||||
6511B41E22CE1B42007B5B23 /* Constants.swift in Sources */,
|
||||
C05FE96C63084AB3DF0DDFDB /* GenerateCommand.swift in Sources */,
|
||||
C05FEDB04CADDE3D52A8CF7D /* ConvertCommand.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
A10CF0B022339D820068E490 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
A10CF0B122339D820068E490 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
A10CF0B322339D820068E490 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 6PQJ88HC63;
|
||||
PRODUCT_NAME = stringen;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
A10CF0B422339D820068E490 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 6PQJ88HC63;
|
||||
PRODUCT_NAME = stringen;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
A10CF0A622339D820068E490 /* Build configuration list for PBXProject "StringGenerator" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
A10CF0B022339D820068E490 /* Debug */,
|
||||
A10CF0B122339D820068E490 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
A10CF0B222339D820068E490 /* Build configuration list for PBXNativeTarget "StringGenerator" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
A10CF0B322339D820068E490 /* Debug */,
|
||||
A10CF0B422339D820068E490 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = A10CF0A322339D820068E490 /* Project object */;
|
||||
}
|
||||
43
StringGenerator/Colors.swift
Normal file
43
StringGenerator/Colors.swift
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Colors.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 04/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol ModeCode {
|
||||
var value: UInt8 { get }
|
||||
}
|
||||
|
||||
public enum Color: UInt8, ModeCode {
|
||||
case black = 30
|
||||
case red
|
||||
case green
|
||||
case yellow
|
||||
case blue
|
||||
case magenta
|
||||
case cyan
|
||||
case white
|
||||
case `default` = 39
|
||||
case lightBlack = 90
|
||||
case lightRed
|
||||
case lightGreen
|
||||
case lightYellow
|
||||
case lightBlue
|
||||
case lightMagenta
|
||||
case lightCyan
|
||||
case lightWhite
|
||||
|
||||
public var value: UInt8 {
|
||||
return rawValue
|
||||
}
|
||||
}
|
||||
extension String {
|
||||
func color(_ color: Color) -> String {
|
||||
return "\u{001B}[0;\(color.rawValue)m\(self)\u{001B}[0;\(Color.default.rawValue)m"
|
||||
}
|
||||
}
|
||||
|
||||
77
StringGenerator/Commands/AddStringCommand.swift
Normal file
77
StringGenerator/Commands/AddStringCommand.swift
Normal file
@ -0,0 +1,77 @@
|
||||
//
|
||||
// AddStringCommand.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 12/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AddStringCommand: CommandProtocol {
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
|
||||
var name: String = "add"
|
||||
|
||||
var description: String = """
|
||||
Adds/Updates localizable strings in plist-files
|
||||
type 'stringen add [Key.Key.Key...] [project resources folder path]'
|
||||
if [Key.Key.Key...] already exists, updates the value
|
||||
"""
|
||||
|
||||
func perform(arguments: [String]) {
|
||||
if arguments.count == 0 {
|
||||
print(error: "no arguments provided")
|
||||
exit(1)
|
||||
}
|
||||
else if arguments.count == 1 {
|
||||
print(error: "too few arguments")
|
||||
exit(1)
|
||||
}
|
||||
else if arguments.count == 2 {
|
||||
let key = arguments[0]
|
||||
let resourcesPath = URL(fileURLWithPath: arguments[1])
|
||||
guard let lprojFoldersPaths = try? fileManager.contentsOfDirectory(atPath: resourcesPath.path)
|
||||
.filter({ $0.contains(Constants.lproj) })
|
||||
.compactMap({ resourcesPath.appendingPathComponent($0) }),
|
||||
!lprojFoldersPaths.isEmpty else {
|
||||
print(error: "*.lproj folders not found.")
|
||||
exit(1)
|
||||
}
|
||||
let stringFiles = lprojFoldersPaths.map { $0.appendingPathComponent(Constants.stringFileName) }
|
||||
.filter({ fileManager.fileExists(atPath: $0.path) })
|
||||
guard stringFiles.count == lprojFoldersPaths.count else {
|
||||
print(error: "\(Constants.stringFileName)'s count is not equal to all languages.")
|
||||
exit(1)
|
||||
}
|
||||
stringFiles.forEach({ addKey(key, fileURL: $0) })
|
||||
}
|
||||
else {
|
||||
print(error: "too many arguments")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
private func addKey(_ key: String, fileURL: URL) {
|
||||
guard var plistDictionary = NSDictionary(contentsOf: fileURL) as? [String: Any] else {
|
||||
print(error: "Can't parse file \(getTwoLastComponents(path: fileURL))")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
print("Enter localized string for \("\(getTwoLastComponents(path: fileURL))".color(.lightCyan))\("".color(.default)).")
|
||||
guard let input = readLine()?.trimmingCharacters(in: .whitespacesAndNewlines) else {
|
||||
addKey(key, fileURL: fileURL)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
plistDictionary[keyPath: key] = input
|
||||
|
||||
if !NSDictionary(dictionary: plistDictionary).write(toFile: fileURL.path, atomically: true) {
|
||||
print(error: fileURL.path.color(.lightCyan) + " create error.")
|
||||
exit(1)
|
||||
}
|
||||
print(success: "\(fileURL.path) successfully created.")
|
||||
}
|
||||
|
||||
}
|
||||
15
StringGenerator/Commands/BaseCommand.swift
Normal file
15
StringGenerator/Commands/BaseCommand.swift
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// BaseCommand.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 04/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol CommandProtocol {
|
||||
var name: String { get }
|
||||
var description: String { get }
|
||||
func perform(arguments: [String])
|
||||
}
|
||||
119
StringGenerator/Commands/ConvertCommand.swift
Normal file
119
StringGenerator/Commands/ConvertCommand.swift
Normal file
@ -0,0 +1,119 @@
|
||||
//
|
||||
// ConvertCommand.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Mike Price on 2019-07-11.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ConvertCommand: CommandProtocol {
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
private lazy var rowRegex: NSRegularExpression = {
|
||||
guard let regex = try? NSRegularExpression(pattern: Constants.localizationRowPattern) else {
|
||||
print(error: "Regexp error.")
|
||||
exit(1)
|
||||
}
|
||||
return regex
|
||||
}()
|
||||
|
||||
var name: String = "convert"
|
||||
var description: String = """
|
||||
Converting all '\(Constants.generatedStringsFileName)' from resources folder to plists for all language (to selected output folder)
|
||||
"""
|
||||
|
||||
func perform(arguments: [String]) {
|
||||
guard arguments.count == 2 else {
|
||||
print(error: "Please, provide resources folder & Localization output folder.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let resourcesPath = URL(fileURLWithPath: arguments[0])
|
||||
let outputPath = URL(fileURLWithPath: arguments[1])
|
||||
|
||||
guard let lprojFoldersPaths = try? fileManager
|
||||
.contentsOfDirectory(atPath: resourcesPath.path)
|
||||
.filter({ $0.contains(Constants.lproj) })
|
||||
.compactMap({ resourcesPath.appendingPathComponent($0) }),
|
||||
!lprojFoldersPaths.isEmpty else {
|
||||
print(error: "*.lproj folders not found.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let langFiles = lprojFoldersPaths.map { $0.appendingPathComponent(Constants.generatedStringsFileName) }
|
||||
.filter({ fileManager.fileExists(atPath: $0.path) })
|
||||
|
||||
guard langFiles.count == lprojFoldersPaths.count else {
|
||||
print(error: "\(Constants.generatedStringsFileName)'s count is not equal to all languages.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
if !fileManager.fileExists(atPath: outputPath.path) {
|
||||
try? fileManager.createDirectory(at: outputPath, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
langFiles.forEach { (url: URL) in
|
||||
guard let fileData = try? String(contentsOfFile: url.path)
|
||||
else {
|
||||
print(error: url.path.color(.lightCyan) + " parsing text error.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let nameWithoutExt: String = url.pathComponents[url.pathComponents.count - 2]
|
||||
var firstName = nameWithoutExt.split(separator: ".").dropLast()
|
||||
firstName.append("plist")
|
||||
let resultFileName = firstName.joined(separator: ".")
|
||||
let stringPlistFilePath = outputPath.appendingPathComponent(resultFileName)
|
||||
|
||||
let dataStrings = fileData.split(separator: "\n")
|
||||
.filter({ rowRegex.matches(String($0)) })
|
||||
.map({ String($0) })
|
||||
|
||||
validate(name: stringPlistFilePath.path, dataStrings: dataStrings)
|
||||
|
||||
var plistData = [String: Any]()
|
||||
parsing(dataStrings, in: &plistData)
|
||||
if !NSDictionary(dictionary: plistData).write(toFile: stringPlistFilePath.path, atomically: true) {
|
||||
print(error: stringPlistFilePath.path.color(.lightCyan) + " create error.")
|
||||
exit(1)
|
||||
}
|
||||
print(success: "\(stringPlistFilePath.path) successfully created.")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private func validate(name: String, dataStrings: [String]) {
|
||||
if !dataStrings.filter({ !rowRegex.matches($0) }).isEmpty {
|
||||
print(error: "\(name) is invalid.")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
private func parsing(_ dataStrings: [String], in result: inout [String: Any]) {
|
||||
dataStrings.forEach { row in
|
||||
let path = getGroup(Constants.rowPathComponent, from: row)
|
||||
let value = getGroup(Constants.rowValueComponent, from: row)
|
||||
result[keyPath: path] = value
|
||||
}
|
||||
}
|
||||
|
||||
private func getGroup(_ key: String, from row: String) -> String {
|
||||
let fullRange = NSRange(row.startIndex..<row.endIndex, in: row)
|
||||
guard let match = rowRegex.firstMatch(in: row, range: fullRange) else {
|
||||
print(error: "Error get full range from '\(row)'")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let valueRange = match.range(withName: key)
|
||||
if valueRange.location != NSNotFound, let valueRangeInRow = Range(valueRange, in: row) {
|
||||
return String(row[valueRangeInRow])
|
||||
}
|
||||
else {
|
||||
print(error: "Invalid value in '\(row)'")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
290
StringGenerator/Commands/GenerateCommand.swift
Normal file
290
StringGenerator/Commands/GenerateCommand.swift
Normal file
@ -0,0 +1,290 @@
|
||||
//
|
||||
// GenerateCommand.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Mike Price on 2019-07-11.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class GenerateCommand: CommandProtocol {
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
|
||||
var name: String = "generate"
|
||||
var description: String = """
|
||||
Parameters: [project resources folder path] [dir for generated enum path]
|
||||
Generation localizable resources on steps:
|
||||
- check on valid and compare all \(Constants.stringFileName) (for all languages) in resources folder
|
||||
- generate \(Constants.generatedFileName) enum for selected directory
|
||||
- generate \(Constants.generatedStringsFileName)'s for all languages
|
||||
"""
|
||||
|
||||
func perform(arguments: [String]) {
|
||||
guard arguments.count == 2 else {
|
||||
print(error: "Please, provide resources folder & Localization output folder.")
|
||||
return
|
||||
}
|
||||
let resourcesPath = URL(fileURLWithPath: arguments[0])
|
||||
let localizationPath = URL(fileURLWithPath: arguments[1])
|
||||
guard let lprojFoldersPaths = try? fileManager.contentsOfDirectory(atPath: resourcesPath.path)
|
||||
.filter({ $0.contains(Constants.lproj) })
|
||||
.compactMap({ resourcesPath.appendingPathComponent($0) }),
|
||||
!lprojFoldersPaths.isEmpty else {
|
||||
print(error: "*.lproj folders not found.")
|
||||
exit(1)
|
||||
}
|
||||
let stringFiles = lprojFoldersPaths.map { $0.appendingPathComponent(Constants.stringFileName) }
|
||||
.filter({ fileManager.fileExists(atPath: $0.path) })
|
||||
|
||||
guard stringFiles.count == lprojFoldersPaths.count else {
|
||||
print(error: "\(Constants.stringFileName)'s count is not equal to all languages.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
validate(stringFiles: stringFiles)
|
||||
compare(stringFiles: stringFiles)
|
||||
makeLocalizationFile(stringFiles[0], at: localizationPath)
|
||||
generationLocalizationStrings(stringFiles: stringFiles)
|
||||
}
|
||||
|
||||
// MARK: Validation block
|
||||
|
||||
private func validate(stringFiles: [URL]) {
|
||||
var isFailure = false
|
||||
for stringFile in stringFiles {
|
||||
guard let plistDictionary = NSDictionary(contentsOf: stringFile) as? [String: Any] else {
|
||||
print(error: "Can't parse the file. Maybe, it's not a plist?")
|
||||
exit(1)
|
||||
}
|
||||
let errorKeys = isValid(dict: plistDictionary)
|
||||
if !errorKeys.isEmpty {
|
||||
print("")
|
||||
print(error: "Plist '\(getTwoLastComponents(path: stringFile).color(.lightCyan))' have next invalid keys:")
|
||||
errorKeys.forEach { print(error: $0) }
|
||||
isFailure = true
|
||||
}
|
||||
}
|
||||
if isFailure {
|
||||
exit(1)
|
||||
}
|
||||
print(success: "Key values in Plists is valid.")
|
||||
}
|
||||
|
||||
private func isValid(dict: [String: Any], keyPath: String = "") -> [String] {
|
||||
var errors = [String]()
|
||||
for item in dict {
|
||||
if let child = item.value as? [String: Any] {
|
||||
if child.isEmpty {
|
||||
errors.append("value for key \("\(keyPath)\(item.key)".color(.lightRed)) is empty.")
|
||||
}
|
||||
else {
|
||||
errors.append(contentsOf: isValid(dict: child, keyPath: "\(keyPath)\(item.key)."))
|
||||
}
|
||||
}
|
||||
else if (item.value as? String)?.isEmpty == true {
|
||||
errors.append("value for key \("\(keyPath)\(item.key)".color(.lightRed)) is empty.")
|
||||
}
|
||||
else if item.value as? [String: Any] == nil && item.value as? String == nil {
|
||||
errors.append("value for key \("\(keyPath)\(item.key)".color(.lightRed)) is not a String.")
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
// MARK: Compare block
|
||||
|
||||
private func compare(stringFiles: [URL]) {
|
||||
var dictionaries: [(String, [String: Any])] = stringFiles.map { fileUrl in
|
||||
guard let plistDictionary = NSDictionary(contentsOf: fileUrl) as? [String: Any] else {
|
||||
print(error: "Can't parse \(getTwoLastComponents(path: fileUrl).color(.lightCyan)). Maybe, it's not a plist?")
|
||||
exit(1)
|
||||
}
|
||||
return (fileUrl.path, plistDictionary)
|
||||
}
|
||||
var equalityResults: [Bool] = []
|
||||
for index in 0...dictionaries.count - 1 {
|
||||
let (firstPlistName, firstPlist) = dictionaries[index]
|
||||
let (secondPlistName, secondPlist) = dictionaries[index != dictionaries.count - 1 ? index + 1 : 0]
|
||||
equalityResults.append(comparePlists(firstName: firstPlistName, firstPlist: firstPlist, secondName: secondPlistName, secondPlist: secondPlist))
|
||||
}
|
||||
if equalityResults.contains(false) {
|
||||
exit(1)
|
||||
}
|
||||
print(success: "Plists keys are equal to each other.")
|
||||
}
|
||||
|
||||
private func comparePlists(firstName: String, firstPlist: [String: Any], secondName: String, secondPlist: [String: Any], keyPath: String = "") -> Bool {
|
||||
var areEqual: Bool = true
|
||||
for item in firstPlist {
|
||||
if let child = item.value as? [String: Any] {
|
||||
if let secondChild = secondPlist[item.key] as? [String: Any] {
|
||||
areEqual = areEqual && comparePlists(firstName: firstName, firstPlist: child, secondName: secondName, secondPlist: secondChild, keyPath: "\(keyPath)\(item.key).")
|
||||
}
|
||||
else {
|
||||
print(error: """
|
||||
plist \("\(getTwoLastComponents(path: secondName))".color(.lightCyan))
|
||||
does not contain\(" \(keyPath)\(item.key) ".color(.lightRed))key.
|
||||
But exists in \("\(getTwoLastComponents(path: firstName))".color(.lightCyan)).
|
||||
""")
|
||||
areEqual = areEqual && comparePlists(firstName: firstName, firstPlist: child, secondName: secondName, secondPlist: secondPlist, keyPath: "\(keyPath)\(item.key).")
|
||||
}
|
||||
}
|
||||
else if item.value as? String != nil {
|
||||
if secondPlist[item.key] as? String == nil {
|
||||
areEqual = false
|
||||
print(error: """
|
||||
plist \("\(getTwoLastComponents(path: secondName))".color(.lightCyan))
|
||||
does not contain\(" \(keyPath)\(item.key) ".color(.lightRed))key.
|
||||
But exists in \("\(getTwoLastComponents(path: firstName))".color(.lightCyan)).
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return areEqual
|
||||
}
|
||||
|
||||
// MARK: LocalizedStringsKeys.swift generation block
|
||||
|
||||
private func makeLocalizationFile(_ path: URL, at localizationPath: URL) {
|
||||
guard let plistDictionary = NSDictionary(contentsOf: path) as? [String: Any] else {
|
||||
print(error: "Can't parse \(getTwoLastComponents(path: path).color(.lightCyan)). Maybe, it's not a plist?")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let startTime = Date()
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "MM/dd/yy h:mm a Z"
|
||||
|
||||
var swiftFileString: String = ""
|
||||
swiftFileString += Constants.comment
|
||||
swiftFileString += "///Generated: \(dateFormatter.string(from: startTime))\n\n"
|
||||
swiftFileString += enumStart(name: Constants.enumName)
|
||||
swiftFileString += parseBody(dict: plistDictionary).0
|
||||
swiftFileString += enumEnd()
|
||||
|
||||
let savePath = localizationPath.appendingPathComponent(Constants.generatedFileName).path
|
||||
if !fileManager.fileExists(atPath: savePath) {
|
||||
fileManager.createFile(atPath: savePath, contents: swiftFileString.data(using: .utf8), attributes: nil)
|
||||
}
|
||||
else {
|
||||
do {
|
||||
try swiftFileString.write(toFile: savePath, atomically: true, encoding: .utf8)
|
||||
print(success: "\(Constants.generatedFileName) enum in '\(localizationPath.path)' was created successfully.")
|
||||
}
|
||||
catch {
|
||||
print(error: error.localizedDescription)
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func parseBody(dict: [String: Any], level: Int = 1, keyPath: String = "") -> (String, Bool) {
|
||||
var res = ""
|
||||
var levelHasKey = false
|
||||
for item in dict {
|
||||
if let child = item.value as? [String: Any] {
|
||||
let tmp = parseBody(dict: child, level: level + 1, keyPath: "\(keyPath)\(item.key).")
|
||||
res += tab(level)
|
||||
res += enumStart(name: item.key, isLocalized: tmp.1)
|
||||
res += tmp.0
|
||||
res += tab(level)
|
||||
res += enumEnd()
|
||||
}
|
||||
if item.value as? String != nil {
|
||||
levelHasKey = true
|
||||
res += tab(level)
|
||||
res += caseFor(name: item.key, value: "\(keyPath)\(item.key)")
|
||||
}
|
||||
}
|
||||
if levelHasKey {
|
||||
res += localized(key: keyPath, level: level)
|
||||
}
|
||||
return (res, levelHasKey)
|
||||
}
|
||||
|
||||
private func enumStart(name: String, isLocalized: Bool = false) -> String {
|
||||
return "enum \(name)\(isLocalized ? ": String, LocalizedProtocol" : "" ) {\n"
|
||||
}
|
||||
|
||||
private func enumEnd() -> String {
|
||||
return "}\n"
|
||||
}
|
||||
|
||||
private func caseFor(name: String, value: String) -> String {
|
||||
return "case \(name) = \"\(value)\"\n"
|
||||
}
|
||||
|
||||
private func tab(_ level: Int) -> String {
|
||||
var res = ""
|
||||
for _ in 0 ... level - 1 {
|
||||
res += "\t"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
private func localized(key: String, level: Int) -> String {
|
||||
var res = "\n"
|
||||
res += tab(level)
|
||||
res += "var localized: String {\n"
|
||||
res += tab(level + 1)
|
||||
res += "return rawValue.localized\n"
|
||||
res += tab(level)
|
||||
res += "}\n"
|
||||
return res
|
||||
}
|
||||
|
||||
// MARK: Localized.strings generation block
|
||||
|
||||
private func generationLocalizationStrings(stringFiles: [URL]) {
|
||||
stringFiles.forEach { url in
|
||||
guard let plistDictionary = NSDictionary(contentsOf: url) as? [String: Any] else {
|
||||
print(error: "Can't parse \(getTwoLastComponents(path: url).color(.lightCyan)). Maybe, it's not a plist?")
|
||||
exit(1)
|
||||
}
|
||||
var content = [String]()
|
||||
|
||||
content.append(Constants.comment + "/// from: \(getTwoLastComponents(path: url))")
|
||||
|
||||
calculationStrings(plistDictionary, content: &content)
|
||||
|
||||
let body = content.enumerated().map({ (index: Int, item: String) -> String in
|
||||
if index == content.count - 1 { return item }
|
||||
return item.split(separator: ".").first == content[index+1].split(separator: ".").first
|
||||
? item
|
||||
: item + "\n"
|
||||
}).joined(separator: "\n")
|
||||
|
||||
let savePath = url.deletingLastPathComponent().appendingPathComponent(Constants.generatedStringsFileName).path
|
||||
|
||||
if !fileManager.fileExists(atPath: savePath) {
|
||||
fileManager.createFile(atPath: savePath, contents: body.data(using: .utf8), attributes: nil)
|
||||
print(success: "\(savePath) was created successfully.")
|
||||
}
|
||||
else {
|
||||
do {
|
||||
try body.write(toFile: savePath, atomically: true, encoding: .utf8)
|
||||
print(success: "\(savePath) was created successfully.")
|
||||
}
|
||||
catch {
|
||||
print(error: error.localizedDescription)
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func calculationStrings(_ dict: [String: Any], content: inout [String], path: String = "") {
|
||||
dict.forEach { key, value in
|
||||
if let value = value as? Dictionary<String, Any> {
|
||||
calculationStrings(value, content: &content, path: "\(path + key).")
|
||||
}
|
||||
else if let value = value as? String {
|
||||
content.append("\"\(path + key)\" = \"\(value.replacingOccurrences(of: "\"", with: "\\\""))\";")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
StringGenerator/Constants.swift
Normal file
22
StringGenerator/Constants.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Constants.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 04/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Constants {
|
||||
static let stringFileName = "strings.plist"
|
||||
static let lproj = ".lproj"
|
||||
static let enumName: String = "Localized"
|
||||
static let generatedFileName = "LocalizedStringsKeys.swift"
|
||||
static let generatedStringsFileName = "Localizable.strings"
|
||||
static let comment = "///This file is automatically generated. Yout must not edit it manually\n"
|
||||
|
||||
static let localizationRowPattern = #"\"(?<path>(.+))\" = \"(?<value>(.+))\";"#
|
||||
static let rowPathComponent = "path"
|
||||
static let rowValueComponent = "value"
|
||||
}
|
||||
88
StringGenerator/Helpers.swift
Normal file
88
StringGenerator/Helpers.swift
Normal file
@ -0,0 +1,88 @@
|
||||
//
|
||||
// Helpers.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 04/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func print(error: String) {
|
||||
print("\("Error".color(.red)): \(error)")
|
||||
}
|
||||
|
||||
func print(success: String) {
|
||||
print(success.color(.green))
|
||||
}
|
||||
|
||||
func getTwoLastComponents(path: String) -> String {
|
||||
return getTwoLastComponents(path: URL(fileURLWithPath: path))
|
||||
}
|
||||
|
||||
func getTwoLastComponents(path: URL) -> String {
|
||||
let excludeCount = path.pathComponents.count - 2
|
||||
return path.pathComponents.dropFirst(excludeCount).joined(separator: "/")
|
||||
}
|
||||
|
||||
extension NSRegularExpression {
|
||||
func matches(_ string: String) -> Bool {
|
||||
let range = NSRange(location: 0, length: string.utf16.count)
|
||||
return firstMatch(in: string, options: [], range: range) != nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary {
|
||||
subscript(keyPath keyPath: String) -> Any? {
|
||||
get {
|
||||
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath)
|
||||
else { return nil }
|
||||
return getValue(forKeyPath: keyPath)
|
||||
}
|
||||
set {
|
||||
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath),
|
||||
let newValue = newValue
|
||||
else { return }
|
||||
self.setValue(newValue, forKeyPath: keyPath)
|
||||
}
|
||||
}
|
||||
|
||||
static private func keyPathKeys(forKeyPath: String) -> [Key]? {
|
||||
let keys = forKeyPath.components(separatedBy: ".").reversed().compactMap({ $0 as? Key })
|
||||
return keys.isEmpty ? nil : keys
|
||||
}
|
||||
|
||||
private func getValue(forKeyPath keyPath: [Key]) -> Any? {
|
||||
guard let lastKey = keyPath.last, let value = self[lastKey]
|
||||
else { return nil }
|
||||
return keyPath.count == 1 ? value : (value as? [Key: Any])
|
||||
.flatMap { $0.getValue(forKeyPath: Array(keyPath.dropLast())) }
|
||||
}
|
||||
|
||||
private mutating func setValue(_ value: Any, forKeyPath keyPath: [Key]) {
|
||||
var keyPath = keyPath
|
||||
guard let firstKey = keyPath.popLast() else { return }
|
||||
if keyPath.isEmpty {
|
||||
self[firstKey] = value as? Value
|
||||
}
|
||||
else {
|
||||
var dict: [Key : Any] = self[firstKey] as? [Key : Any] ?? [:]
|
||||
deepSet(value, keyPath: keyPath, in: &dict)
|
||||
self[firstKey] = dict as? Value
|
||||
}
|
||||
}
|
||||
|
||||
private func deepSet(_ value: Any, keyPath: [Key], in dict: inout [Key : Any]) {
|
||||
var keyPath = keyPath
|
||||
guard let key: Key = keyPath.popLast() else {
|
||||
return
|
||||
}
|
||||
if keyPath.isEmpty {
|
||||
dict[key] = value
|
||||
return
|
||||
}
|
||||
var bufferDict: [Key: Any] = dict[key] as? [Key : Any] ?? [:]
|
||||
deepSet(value, keyPath: keyPath, in: &bufferDict)
|
||||
dict[key] = bufferDict
|
||||
}
|
||||
}
|
||||
49
StringGenerator/main.swift
Normal file
49
StringGenerator/main.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// LocalizedStringKeyGen.swift
|
||||
// TaxFree
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 05/03/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Run {
|
||||
var commands: [CommandProtocol] = []
|
||||
func perform(arguments: [String]) {
|
||||
guard arguments.count > 0, let commandName = arguments.first, let command = commands.first(where: { (row) -> Bool in
|
||||
return row.name == commandName
|
||||
}) else {
|
||||
showHelp()
|
||||
return
|
||||
}
|
||||
var args = arguments
|
||||
args.removeFirst()
|
||||
command.perform(arguments: args)
|
||||
}
|
||||
|
||||
private func showHelp() {
|
||||
for command in commands {
|
||||
var desc = ""
|
||||
for (index, key) in command.description.split(separator: "\n").enumerated() {
|
||||
desc.append(String(format: index == 0 ? "%s%@\n" : "%-16s%@\n", ("" as NSString).utf8String!, String(key)))
|
||||
}
|
||||
let str = String(format: "%-22s\("".color(.default)) - %@", (command.name.color(.green) as NSString).utf8String!, desc)
|
||||
print(str)
|
||||
}
|
||||
}
|
||||
|
||||
func command<T>(class: T.Type) -> T? {
|
||||
return commands.first(where: { command in
|
||||
return command is T
|
||||
}) as? T
|
||||
}
|
||||
}
|
||||
|
||||
let run = Run()
|
||||
run.commands.append(GenerateCommand())
|
||||
run.commands.append(AddStringCommand())
|
||||
run.commands.append(ConvertCommand())
|
||||
var arguments = CommandLine.arguments
|
||||
arguments.removeFirst()
|
||||
run.perform(arguments: arguments)
|
||||
|
||||
Reference in New Issue
Block a user