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