From ec30280547647a9a801018bfe3593edfa83bb8ab Mon Sep 17 00:00:00 2001 From: Nonchalant Date: Wed, 6 Jun 2018 03:17:27 +0900 Subject: [PATCH] :rocket: initial commit --- .gitignore | 23 + .swift-version | 1 + .travis.yml | 5 + FactoryProvider.podspec | 32 ++ FactoryProvider.xcodeproj/project.pbxproj | 351 ++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + Generator/.gitignore | 4 + Generator/Makefile | 19 + Generator/Package.resolved | 142 ++++++ Generator/Package.swift | 48 ++ Generator/Sources/Core/Element.swift | 9 + Generator/Sources/Core/Enum.swift | 21 + Generator/Sources/Core/EnumCase.swift | 19 + Generator/Sources/Core/Generic.swift | 17 + Generator/Sources/Core/Options.swift | 19 + Generator/Sources/Core/Protocol.swift | 19 + Generator/Sources/Core/Struct.swift | 21 + Generator/Sources/Core/Type.swift | 13 + Generator/Sources/Core/TypeName.swift | 17 + Generator/Sources/Core/Variable.swift | 17 + Generator/Sources/Core/Version.swift | 11 + Generator/Sources/FactoryGenerator/main.swift | 40 ++ Generator/Sources/Generator/Arguments.swift | 21 + .../Sources/Generator/CodeGenerator.swift | 17 + .../Sources/Generator/FileGenerator.swift | 16 + .../Sources/Generator/GenerateError.swift | 11 + Generator/Sources/Generator/Generator.swift | 32 ++ Generator/Sources/Generator/Template.swift | 88 ++++ Generator/Sources/Generator/Types.swift | 19 + Generator/Sources/Parser/ConformParser.swift | 16 + Generator/Sources/Parser/ElementParser.swift | 29 ++ Generator/Sources/Parser/EnumCaseParser.swift | 75 +++ Generator/Sources/Parser/EnumParser.swift | 20 + Generator/Sources/Parser/GenericParser.swift | 50 ++ Generator/Sources/Parser/ParseError.swift | 12 + Generator/Sources/Parser/Parser.swift | 30 ++ Generator/Sources/Parser/ProtocolParser.swift | 16 + Generator/Sources/Parser/StructParser.swift | 21 + Generator/Sources/Parser/TypeParser.swift | 30 ++ Generator/Sources/Parser/TypesParser.swift | 65 +++ Generator/Sources/Parser/VariableParser.swift | 46 ++ .../GeneratorTests/CodeGeneratorTests.swift | 447 ++++++++++++++++++ .../Tests/GeneratorTests/TemplateHelper.swift | 38 ++ .../Tests/ParserTests/TypeParserTests.swift | 405 ++++++++++++++++ LICENSE.md | 22 + Makefile | 8 + README.md | 143 ++++++ Source/Lens.swift | 42 ++ Source/Providable.swift | 139 ++++++ Source/Supporting Files/FactoryFactory.h | 19 + Source/Supporting Files/Info.plist | 24 + 52 files changed, 2764 insertions(+) create mode 100644 .gitignore create mode 100644 .swift-version create mode 100644 .travis.yml create mode 100644 FactoryProvider.podspec create mode 100644 FactoryProvider.xcodeproj/project.pbxproj create mode 100644 FactoryProvider.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 FactoryProvider.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Generator/.gitignore create mode 100644 Generator/Makefile create mode 100644 Generator/Package.resolved create mode 100644 Generator/Package.swift create mode 100644 Generator/Sources/Core/Element.swift create mode 100644 Generator/Sources/Core/Enum.swift create mode 100644 Generator/Sources/Core/EnumCase.swift create mode 100644 Generator/Sources/Core/Generic.swift create mode 100644 Generator/Sources/Core/Options.swift create mode 100644 Generator/Sources/Core/Protocol.swift create mode 100644 Generator/Sources/Core/Struct.swift create mode 100644 Generator/Sources/Core/Type.swift create mode 100644 Generator/Sources/Core/TypeName.swift create mode 100644 Generator/Sources/Core/Variable.swift create mode 100644 Generator/Sources/Core/Version.swift create mode 100644 Generator/Sources/FactoryGenerator/main.swift create mode 100644 Generator/Sources/Generator/Arguments.swift create mode 100644 Generator/Sources/Generator/CodeGenerator.swift create mode 100644 Generator/Sources/Generator/FileGenerator.swift create mode 100644 Generator/Sources/Generator/GenerateError.swift create mode 100644 Generator/Sources/Generator/Generator.swift create mode 100644 Generator/Sources/Generator/Template.swift create mode 100644 Generator/Sources/Generator/Types.swift create mode 100644 Generator/Sources/Parser/ConformParser.swift create mode 100644 Generator/Sources/Parser/ElementParser.swift create mode 100644 Generator/Sources/Parser/EnumCaseParser.swift create mode 100644 Generator/Sources/Parser/EnumParser.swift create mode 100644 Generator/Sources/Parser/GenericParser.swift create mode 100644 Generator/Sources/Parser/ParseError.swift create mode 100644 Generator/Sources/Parser/Parser.swift create mode 100644 Generator/Sources/Parser/ProtocolParser.swift create mode 100644 Generator/Sources/Parser/StructParser.swift create mode 100644 Generator/Sources/Parser/TypeParser.swift create mode 100644 Generator/Sources/Parser/TypesParser.swift create mode 100644 Generator/Sources/Parser/VariableParser.swift create mode 100644 Generator/Tests/GeneratorTests/CodeGeneratorTests.swift create mode 100644 Generator/Tests/GeneratorTests/TemplateHelper.swift create mode 100644 Generator/Tests/ParserTests/TypeParserTests.swift create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 Source/Lens.swift create mode 100644 Source/Providable.swift create mode 100644 Source/Supporting Files/FactoryFactory.h create mode 100644 Source/Supporting Files/Info.plist diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1de6de8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Xcode + +.build/ +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + +# FactoryProvider + +generate \ No newline at end of file diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..8a36cd1 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +4.1 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..46b8516 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: swift +osx_image: xcode9.3 + +script: + - cd Generator && swift test \ No newline at end of file diff --git a/FactoryProvider.podspec b/FactoryProvider.podspec new file mode 100644 index 0000000..3cddfb4 --- /dev/null +++ b/FactoryProvider.podspec @@ -0,0 +1,32 @@ +Pod::Spec.new do |s| + s.name = "FactoryProvider" + s.version = "0.1.0" + s.summary = "FactoryProvider - generate boilerplate of factory Swift framework." + s.description = <<-DESC + FactoryProvider is a framework to generate boilerplate of factory with an easy to write TestCase. + It generates factories automatically to enable this functionality. + DESC + + s.homepage = "https://github.com/Nonchalant/FactoryProvider" + s.license = 'MIT' + s.author = { "Takeshi Ihara" => "afrontier829@gmail.com" } + s.source = { + :git => "https://github.com/Nonchalant/FactoryProvider.git", + :tag => s.version.to_s + } + + s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.9' + s.watchos.deployment_target = '2.0' + s.tvos.deployment_target = '9.0' + s.source_files = ['Source/**/*.swift'] + generator_name = 'generate' + s.preserve_paths = ['Generator/**/*', generator_name] + s.prepare_command = <<-CMD + curl -Lo #{generator_name} https://github.com/Nonchalant/FactoryProvider/releases/download/#{s.version}/#{generator_name} + chmod +x #{generator_name} + CMD + s.frameworks = 'Foundation' + s.requires_arc = true + s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO', 'SWIFT_REFLECTION_METADATA_LEVEL' => 'none' } +end \ No newline at end of file diff --git a/FactoryProvider.xcodeproj/project.pbxproj b/FactoryProvider.xcodeproj/project.pbxproj new file mode 100644 index 0000000..645e32c --- /dev/null +++ b/FactoryProvider.xcodeproj/project.pbxproj @@ -0,0 +1,351 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 245D5E1220C6FC0F009DC9EE /* FactoryProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 245D5E1020C6FC0F009DC9EE /* FactoryProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 245D5E1A20C6FCC4009DC9EE /* Providable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 245D5E1920C6FCC4009DC9EE /* Providable.swift */; }; + 604121FE20CDAFBF00FCBE7A /* Lens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604121FD20CDAFBF00FCBE7A /* Lens.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 245D5E0D20C6FC0F009DC9EE /* FactoryProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FactoryProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 245D5E1020C6FC0F009DC9EE /* FactoryProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FactoryProvider.h; sourceTree = ""; }; + 245D5E1120C6FC0F009DC9EE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 245D5E1920C6FCC4009DC9EE /* Providable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Providable.swift; sourceTree = ""; }; + 604121FD20CDAFBF00FCBE7A /* Lens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lens.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 245D5E0920C6FC0F009DC9EE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 245D5E0320C6FC0F009DC9EE = { + isa = PBXGroup; + children = ( + 245D5E0F20C6FC0F009DC9EE /* Source */, + 245D5E0E20C6FC0F009DC9EE /* Products */, + ); + sourceTree = ""; + }; + 245D5E0E20C6FC0F009DC9EE /* Products */ = { + isa = PBXGroup; + children = ( + 245D5E0D20C6FC0F009DC9EE /* FactoryProvider.framework */, + ); + name = Products; + sourceTree = ""; + }; + 245D5E0F20C6FC0F009DC9EE /* Source */ = { + isa = PBXGroup; + children = ( + 245D5E1820C6FC9D009DC9EE /* Supporting Files */, + 245D5E1920C6FCC4009DC9EE /* Providable.swift */, + 604121FD20CDAFBF00FCBE7A /* Lens.swift */, + ); + path = Source; + sourceTree = ""; + }; + 245D5E1820C6FC9D009DC9EE /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 245D5E1020C6FC0F009DC9EE /* FactoryProvider.h */, + 245D5E1120C6FC0F009DC9EE /* Info.plist */, + ); + path = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 245D5E0A20C6FC0F009DC9EE /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 245D5E1220C6FC0F009DC9EE /* FactoryProvider.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 245D5E0C20C6FC0F009DC9EE /* FactoryProvider */ = { + isa = PBXNativeTarget; + buildConfigurationList = 245D5E1520C6FC0F009DC9EE /* Build configuration list for PBXNativeTarget "FactoryProvider" */; + buildPhases = ( + 245D5E0820C6FC0F009DC9EE /* Sources */, + 245D5E0920C6FC0F009DC9EE /* Frameworks */, + 245D5E0A20C6FC0F009DC9EE /* Headers */, + 245D5E0B20C6FC0F009DC9EE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FactoryProvider; + productName = FactoryProvider; + productReference = 245D5E0D20C6FC0F009DC9EE /* FactoryProvider.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 245D5E0420C6FC0F009DC9EE /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0940; + ORGANIZATIONNAME = Nonchalant; + TargetAttributes = { + 245D5E0C20C6FC0F009DC9EE = { + CreatedOnToolsVersion = 9.4; + LastSwiftMigration = 0940; + }; + }; + }; + buildConfigurationList = 245D5E0720C6FC0F009DC9EE /* Build configuration list for PBXProject "FactoryProvider" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 245D5E0320C6FC0F009DC9EE; + productRefGroup = 245D5E0E20C6FC0F009DC9EE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 245D5E0C20C6FC0F009DC9EE /* FactoryProvider */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 245D5E0B20C6FC0F009DC9EE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 245D5E0820C6FC0F009DC9EE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 245D5E1A20C6FCC4009DC9EE /* Providable.swift in Sources */, + 604121FE20CDAFBF00FCBE7A /* Lens.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 245D5E1320C6FC0F009DC9EE /* 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 = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 245D5E1420C6FC0F009DC9EE /* 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 = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 245D5E1620C6FC0F009DC9EE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.nonchalant.FactoryProvider; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 245D5E1720C6FC0F009DC9EE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.nonchalant.FactoryProvider; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 245D5E0720C6FC0F009DC9EE /* Build configuration list for PBXProject "FactoryProvider" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 245D5E1320C6FC0F009DC9EE /* Debug */, + 245D5E1420C6FC0F009DC9EE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 245D5E1520C6FC0F009DC9EE /* Build configuration list for PBXNativeTarget "FactoryProvider" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 245D5E1620C6FC0F009DC9EE /* Debug */, + 245D5E1720C6FC0F009DC9EE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 245D5E0420C6FC0F009DC9EE /* Project object */; +} diff --git a/FactoryProvider.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/FactoryProvider.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..5885a87 --- /dev/null +++ b/FactoryProvider.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/FactoryProvider.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/FactoryProvider.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/FactoryProvider.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Generator/.gitignore b/Generator/.gitignore new file mode 100644 index 0000000..6b7b150 --- /dev/null +++ b/Generator/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj \ No newline at end of file diff --git a/Generator/Makefile b/Generator/Makefile new file mode 100644 index 0000000..db8ab3c --- /dev/null +++ b/Generator/Makefile @@ -0,0 +1,19 @@ +.PHONY : clean build test release xcodeproj + +default: clean build + +clean: + swift package clean + rm -rf .build ./FactoryGenerator.xcodeproj + +build: + swift build + +test: + swift test + +release: + swift build -c release -Xswiftc -static-stdlib + +xcodeproj: + swift package generate-xcodeproj diff --git a/Generator/Package.resolved b/Generator/Package.resolved new file mode 100644 index 0000000..cf315bc --- /dev/null +++ b/Generator/Package.resolved @@ -0,0 +1,142 @@ +{ + "object": { + "pins": [ + { + "package": "Clang_C", + "repositoryURL": "https://github.com/norio-nomura/Clang_C.git", + "state": { + "branch": null, + "revision": "7b7d3eed8080d14ba39c50e29fa8c7b051bcf969", + "version": "1.0.3" + } + }, + { + "package": "Commandant", + "repositoryURL": "https://github.com/Carthage/Commandant.git", + "state": { + "branch": null, + "revision": "7f29606ec3a2054a601f0e72f562a104dbc1a11a", + "version": "0.13.0" + } + }, + { + "package": "Commander", + "repositoryURL": "https://github.com/kylef/Commander", + "state": { + "branch": null, + "revision": "e5b50ad7b2e91eeb828393e89b03577b16be7db9", + "version": "0.8.0" + } + }, + { + "package": "MirrorDiffKit", + "repositoryURL": "https://github.com/Kuniwak/MirrorDiffKit.git", + "state": { + "branch": null, + "revision": "f216990445d98f9ce1d0e23523d19686cb56f340", + "version": "3.0.1" + } + }, + { + "package": "Nimble", + "repositoryURL": "https://github.com/Quick/Nimble.git", + "state": { + "branch": null, + "revision": "8023e3980d91b470ad073d6da843b73f2eeb1844", + "version": "7.1.2" + } + }, + { + "package": "PathKit", + "repositoryURL": "https://github.com/kylef/PathKit.git", + "state": { + "branch": null, + "revision": "fa81fa9e3a9f59645159c4ea45c0c46ee6558f71", + "version": "0.9.1" + } + }, + { + "package": "Quick", + "repositoryURL": "https://github.com/Quick/Quick.git", + "state": { + "branch": null, + "revision": "3e3023569c8d4c4a0d000f58db765df53041117f", + "version": "1.3.0" + } + }, + { + "package": "Result", + "repositoryURL": "https://github.com/antitypical/Result.git", + "state": { + "branch": null, + "revision": "7477584259bfce2560a19e06ad9f71db441fff11", + "version": "3.2.4" + } + }, + { + "package": "SourceKit", + "repositoryURL": "https://github.com/norio-nomura/SourceKit.git", + "state": { + "branch": null, + "revision": "18eaa67ca44443bbe39646916792b9f0c98dbaa1", + "version": "1.0.1" + } + }, + { + "package": "SourceKitten", + "repositoryURL": "https://github.com/jpsim/SourceKitten.git", + "state": { + "branch": null, + "revision": "7c09176766d4bbc5da377ad857953fb49510a6aa", + "version": "0.21.0" + } + }, + { + "package": "Spectre", + "repositoryURL": "https://github.com/kylef/Spectre.git", + "state": { + "branch": null, + "revision": "e34d5687e1e9d865e3527dd58bc2f7464ef6d936", + "version": "0.8.0" + } + }, + { + "package": "Stencil", + "repositoryURL": "https://github.com/kylef/Stencil.git", + "state": { + "branch": null, + "revision": "c2e25f25acfbe24442809c055141d8323af8f6cc", + "version": "0.11.0" + } + }, + { + "package": "StencilSwiftKit", + "repositoryURL": "https://github.com/SwiftGen/StencilSwiftKit.git", + "state": { + "branch": null, + "revision": "f15c445ec3c7c5ad44814285e828330bc255bda3", + "version": "2.5.0" + } + }, + { + "package": "SWXMLHash", + "repositoryURL": "https://github.com/drmohundro/SWXMLHash.git", + "state": { + "branch": null, + "revision": "2211b35c2e0e8b08493f86ba52b26e530cabb751", + "version": "4.7.0" + } + }, + { + "package": "Yams", + "repositoryURL": "https://github.com/jpsim/Yams.git", + "state": { + "branch": null, + "revision": "6652aa7b793d3c8a075db0614acb575fcaecf457", + "version": "0.7.0" + } + } + ] + }, + "version": 1 +} diff --git a/Generator/Package.swift b/Generator/Package.swift new file mode 100644 index 0000000..69d7b2a --- /dev/null +++ b/Generator/Package.swift @@ -0,0 +1,48 @@ +// swift-tools-version:4.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "FactoryGenerator", + dependencies: [ + .package(url: "https://github.com/kylef/Commander.git", .upToNextMinor(from: "0.8.0")), + .package(url: "https://github.com/kylef/PathKit.git", .upToNextMinor(from: "0.9.1")), + .package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.21.0")), + .package(url: "https://github.com/SwiftGen/StencilSwiftKit.git", from: "2.4.0"), + .package(url: "https://github.com/Kuniwak/MirrorDiffKit.git", from: "3.0.1") + ], + targets: [ + .target( + name: "FactoryGenerator", + dependencies: ["Commander", "Core", "Generator", "Parser"] + ), + .target( + name: "Core" + ), + .target( + name: "Parser", + dependencies: ["Core", "SourceKittenFramework"] + ), + .target( + name: "Generator", + dependencies: ["Core", "PathKit", "StencilSwiftKit"] + ), + .testTarget( + name: "ParserTests", + dependencies: [ + "Parser", + "MirrorDiffKit" + ], + path: "Tests/ParserTests" + ), + .testTarget( + name: "GeneratorTests", + dependencies: [ + "Generator", + "MirrorDiffKit" + ], + path: "Tests/GeneratorTests" + ) + ] +) diff --git a/Generator/Sources/Core/Element.swift b/Generator/Sources/Core/Element.swift new file mode 100644 index 0000000..9402a4d --- /dev/null +++ b/Generator/Sources/Core/Element.swift @@ -0,0 +1,9 @@ +// +// Element.swift +// Core +// +// Created by Ihara Takeshi on 2018/06/08. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public protocol Element {} diff --git a/Generator/Sources/Core/Enum.swift b/Generator/Sources/Core/Enum.swift new file mode 100644 index 0000000..be53036 --- /dev/null +++ b/Generator/Sources/Core/Enum.swift @@ -0,0 +1,21 @@ +// +// Enum.swift +// Core +// +// Created by Ihara Takeshi on 2018/06/08. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public struct Enum: Type { + public let name: String + public let generics: [Generic] + public let conforms: [TypeName] + public let cases: [Case] + + public init(name: String, generics: [Generic], conforms: [TypeName], cases: [Case]) { + self.name = name + self.generics = generics + self.conforms = conforms + self.cases = cases + } +} diff --git a/Generator/Sources/Core/EnumCase.swift b/Generator/Sources/Core/EnumCase.swift new file mode 100644 index 0000000..f887a47 --- /dev/null +++ b/Generator/Sources/Core/EnumCase.swift @@ -0,0 +1,19 @@ +// +// EnumCase.swift +// Core +// +// Created by Ihara Takeshi on 2018/06/08. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +extension Enum { + public struct Case: Element { + let name: String + let variables: [Variable] + + public init(name: String, variables: [Variable]) { + self.name = name + self.variables = variables + } + } +} diff --git a/Generator/Sources/Core/Generic.swift b/Generator/Sources/Core/Generic.swift new file mode 100644 index 0000000..a48319c --- /dev/null +++ b/Generator/Sources/Core/Generic.swift @@ -0,0 +1,17 @@ +// +// Generic.swift +// Core +// +// Created by Takeshi Ihara on 2018/06/11. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public struct Generic: Element { + public let name: String + public let conforms: [TypeName] + + public init(name: String, conforms: [TypeName]) { + self.name = name + self.conforms = conforms + } +} diff --git a/Generator/Sources/Core/Options.swift b/Generator/Sources/Core/Options.swift new file mode 100644 index 0000000..8e79d78 --- /dev/null +++ b/Generator/Sources/Core/Options.swift @@ -0,0 +1,19 @@ +// +// Options.swift +// Core +// +// Created by Ihara Takeshi on 2018/06/10. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public struct Options { + public let inputs: [String] + public let testables: [String] + public let output: String + + public init(includes: [String], excludes: [String], testables: [String], output: String) { + self.inputs = Array(Set(includes).subtracting(Set(excludes))) + self.testables = Array(Set(testables)) + self.output = output + } +} diff --git a/Generator/Sources/Core/Protocol.swift b/Generator/Sources/Core/Protocol.swift new file mode 100644 index 0000000..80f5e9b --- /dev/null +++ b/Generator/Sources/Core/Protocol.swift @@ -0,0 +1,19 @@ +// +// Protocol.swift +// Core +// +// Created by Ihara Takeshi on 2018/06/09. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public struct Protocol: Type { + public let name: String + public let generics: [Generic] + public let conforms: [TypeName] + + public init(name: String, generics: [Generic], conforms: [TypeName]) { + self.name = name + self.generics = generics + self.conforms = conforms + } +} diff --git a/Generator/Sources/Core/Struct.swift b/Generator/Sources/Core/Struct.swift new file mode 100644 index 0000000..1770275 --- /dev/null +++ b/Generator/Sources/Core/Struct.swift @@ -0,0 +1,21 @@ +// +// Struct.swift +// Core +// +// Created by Ihara Takeshi on 2018/06/06. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public struct Struct: Type { + public let name: String + public let generics: [Generic] + public let conforms: [TypeName] + public let variables: [Variable] + + public init(name: String, generics: [Generic], conforms: [TypeName], variables: [Variable]) { + self.name = name + self.generics = generics + self.conforms = conforms + self.variables = variables + } +} diff --git a/Generator/Sources/Core/Type.swift b/Generator/Sources/Core/Type.swift new file mode 100644 index 0000000..c287fc5 --- /dev/null +++ b/Generator/Sources/Core/Type.swift @@ -0,0 +1,13 @@ +// +// Type.swift +// Core +// +// Created by Ihara Takeshi on 2018/06/06. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public protocol Type { + var name: String { get } + var generics: [Generic] { get } + var conforms: [TypeName] { get } +} diff --git a/Generator/Sources/Core/TypeName.swift b/Generator/Sources/Core/TypeName.swift new file mode 100644 index 0000000..9c28ba9 --- /dev/null +++ b/Generator/Sources/Core/TypeName.swift @@ -0,0 +1,17 @@ +// +// TypeName.swift +// Core +// +// Created by Takeshi Ihara on 2018/06/11. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Foundation + +public struct TypeName: Element { + public let name: String + + public init(name: String) { + self.name = name.split(separator: "&").first?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + } +} diff --git a/Generator/Sources/Core/Variable.swift b/Generator/Sources/Core/Variable.swift new file mode 100644 index 0000000..5c42bb7 --- /dev/null +++ b/Generator/Sources/Core/Variable.swift @@ -0,0 +1,17 @@ +// +// Variable.swift +// Core +// +// Created by Ihara Takeshi on 2018/06/06. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public struct Variable: Element { + public let name: String + public let typeName: TypeName + + public init(name: String, typeName: TypeName) { + self.name = name + self.typeName = typeName + } +} diff --git a/Generator/Sources/Core/Version.swift b/Generator/Sources/Core/Version.swift new file mode 100644 index 0000000..b3cd809 --- /dev/null +++ b/Generator/Sources/Core/Version.swift @@ -0,0 +1,11 @@ +// +// Version.swift +// Core +// +// Created by Ihara Takeshi on 2018/06/11. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public struct Version { + public static let current = "0.1.0" +} diff --git a/Generator/Sources/FactoryGenerator/main.swift b/Generator/Sources/FactoryGenerator/main.swift new file mode 100644 index 0000000..4205dda --- /dev/null +++ b/Generator/Sources/FactoryGenerator/main.swift @@ -0,0 +1,40 @@ +// +// main.swift +// FactoryGenerator +// +// Created by Ihara Takeshi on 2018/06/06. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Commander +import Core +import Generator +import Parser + +let main = command( + VariadicOption("exclude", description: "The path of excluded *.swift"), + VariadicOption("testable", description: "The name of testable target"), + Option("output", default: "./Factories.generated.swift", description: "The generated path of output"), + VariadicArgument("The path of input *.swift") +) { excludes, testables, output, includes in + let options = Options(includes: includes, excludes: excludes, testables: testables, output: output) + + do { + let types = try Parser(paths: options.inputs).run() + try Generator(types: types).run(with: options) + } catch let error { + switch error { + case _ as ParseError: + print("Parse Error is occured 😱") + case _ as GenerateError: + print("Generate Error is occured 😱") + default: + print("Unknown Error is occured 😱") + } + return + } + + print("\(options.output) is generated 🎉") +} + +main.run(Version.current) diff --git a/Generator/Sources/Generator/Arguments.swift b/Generator/Sources/Generator/Arguments.swift new file mode 100644 index 0000000..726e622 --- /dev/null +++ b/Generator/Sources/Generator/Arguments.swift @@ -0,0 +1,21 @@ +// +// Arguments.swift +// Generator +// +// Created by Ihara Takeshi on 2018/06/07. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core + +struct Arguments { + let types: Types + let testables: [String] + + var dictionary: [String: Any] { + return [ + "types": types, + "testables": testables + ] + } +} diff --git a/Generator/Sources/Generator/CodeGenerator.swift b/Generator/Sources/Generator/CodeGenerator.swift new file mode 100644 index 0000000..767174d --- /dev/null +++ b/Generator/Sources/Generator/CodeGenerator.swift @@ -0,0 +1,17 @@ +// +// CodeGenerator.swift +// Generator +// +// Created by Ihara Takeshi on 2018/06/10. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import StencilSwiftKit + +struct CodeGenerator { + static func run(types: [Type], by template: Template, with options: Options) throws -> String { + let arguments = Arguments(types: Types(types: types), testables: options.testables) + return try StencilSwiftTemplate(templateString: template.rawValue).render(arguments.dictionary) + } +} diff --git a/Generator/Sources/Generator/FileGenerator.swift b/Generator/Sources/Generator/FileGenerator.swift new file mode 100644 index 0000000..8051384 --- /dev/null +++ b/Generator/Sources/Generator/FileGenerator.swift @@ -0,0 +1,16 @@ +// +// FileGenerator.swift +// Generator +// +// Created by Ihara Takeshi on 2018/06/10. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import PathKit + +struct FileGenerator { + static func run(render: String, with options: Options) throws { + try Path(options.output).write(render) + } +} diff --git a/Generator/Sources/Generator/GenerateError.swift b/Generator/Sources/Generator/GenerateError.swift new file mode 100644 index 0000000..57800a2 --- /dev/null +++ b/Generator/Sources/Generator/GenerateError.swift @@ -0,0 +1,11 @@ +// +// GenerateError.swift +// Generator +// +// Created by Ihara Takeshi on 2018/06/07. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public enum GenerateError: Swift.Error { + case render +} diff --git a/Generator/Sources/Generator/Generator.swift b/Generator/Sources/Generator/Generator.swift new file mode 100644 index 0000000..c755c65 --- /dev/null +++ b/Generator/Sources/Generator/Generator.swift @@ -0,0 +1,32 @@ +// +// Generator.swift +// Generator +// +// Created by Ihara Takeshi on 2018/06/07. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import PathKit +import StencilSwiftKit + +public struct Generator { + private let types: [Type] + + public init(types: [Type]) { + self.types = types + } + + public func run(with options: Options) throws { + do { + let render = try Template.allCases + .map { + try CodeGenerator.run(types: types, by: $0, with: options) + } + .reduce("") { "\($0)\($1)" } + try FileGenerator.run(render: render, with: options) + } catch { + throw GenerateError.render + } + } +} diff --git a/Generator/Sources/Generator/Template.swift b/Generator/Sources/Generator/Template.swift new file mode 100644 index 0000000..91bbd11 --- /dev/null +++ b/Generator/Sources/Generator/Template.swift @@ -0,0 +1,88 @@ +// +// Template.swift +// Generator +// +// Created by Ihara Takeshi on 2018/06/07. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core + +enum Template { + case header + case factory + case lens + + var rawValue: String { + switch self { + case .header: + return """ + // Generated using FactoryProvider \(Version.current) — https://github.com/Nonchalant/FactoryProvider + // DO NOT EDIT + + import FactoryProvider + {% for testable in testables %} + @testable import {{ testable }} + {% endfor %} + + + """ + case .factory: + return """ + // MARK: - Factory + + // MARK: - Enum + + {% for enum in types.enums where not enum.cases.count == 0 %} + extension {{ enum.name }}: Providable{% if enum.generics.count != 0 %} where{% for generic in enum.generics %} {{ generic.name }}: Providable{% if not forloop.last %},{% endif %}{% endfor %}{% endif %} { + public static func provide() -> {{ enum.name }} { + return .{{ enum.cases.first.name }}{% if not enum.cases.first.variables.count == 0 %}( + {% for variable in enum.cases.first.variables %} + {% if not variable.name == "" %}{{ variable.name }}: {% endif %}{{ variable.typeName.name }}.provide(){% if not forloop.last %},{% endif %} + {% endfor %} + ){% endif %} + } + } + + {% endfor %} + // MARK: - Struct + + {% for struct in types.structs %} + extension {{ struct.name }}: Providable{% if struct.generics.count != 0 %} where{% for generic in struct.generics %} {{ generic.name }}: Providable{% if not forloop.last %},{% endif %}{% endfor %}{% endif %} { + public static func provide() -> {{ struct.name }} { + return {{ struct.name }}( + {% for variable in struct.variables %} + {{ variable.name }}: {{ variable.typeName.name }}.provide(){% if not forloop.last %},{% endif %} + {% endfor %} + ) + } + } + + {% endfor %} + """ + case .lens: + return """ + // MARK: - Lens + + {% for struct in types.structs %} + extension {{ struct.name }} { + {% for variable in struct.variables %} + static var _{{ variable.name }}: Lens<{{ struct.name }}, {{ variable.typeName.name }}> { + return Lens<{{ struct.name }}, {{ variable.typeName.name }}>( + getter: { $0.{{ variable.name }} }, + setter: { base, {{ variable.name }} in + {{ struct.name }}({% for argument in struct.variables %}{{ argument.name }}: {% if variable.name == argument.name %}{{ variable.name }}{% else %}base.{{ argument.name }}{% endif %}{% if not forloop.last %}, {% endif %}{% endfor %}) + } + ) + }{% endfor %} + } + + {% endfor %} + """ + } + } + + static var allCases: [Template] { + return [.header, .factory, .lens] + } +} diff --git a/Generator/Sources/Generator/Types.swift b/Generator/Sources/Generator/Types.swift new file mode 100644 index 0000000..29de215 --- /dev/null +++ b/Generator/Sources/Generator/Types.swift @@ -0,0 +1,19 @@ +// +// Types.swift +// Generator +// +// Created by Ihara Takeshi on 2018/06/09. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core + +struct Types { + let enums: [Enum] + let structs: [Struct] + + init(types: [Type]) { + self.enums = types.compactMap { $0 as? Enum } + self.structs = types.compactMap { $0 as? Struct } + } +} diff --git a/Generator/Sources/Parser/ConformParser.swift b/Generator/Sources/Parser/ConformParser.swift new file mode 100644 index 0000000..f641e9b --- /dev/null +++ b/Generator/Sources/Parser/ConformParser.swift @@ -0,0 +1,16 @@ +// +// ConformParser.swift +// Parser +// +// Created by Takeshi Ihara on 2018/06/11. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import SourceKittenFramework + +struct ConformParser: ElementParser { + static func parse(structure: [String : SourceKitRepresentable], raw: File) -> TypeName? { + return (structure[SwiftDocKey.name.rawValue] as? String).flatMap(TypeName.init) + } +} diff --git a/Generator/Sources/Parser/ElementParser.swift b/Generator/Sources/Parser/ElementParser.swift new file mode 100644 index 0000000..79ece93 --- /dev/null +++ b/Generator/Sources/Parser/ElementParser.swift @@ -0,0 +1,29 @@ +// +// ElementsParser.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/08. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import SourceKittenFramework + +protocol ElementParser { + associatedtype T: Element + static func parse(structure: [String: SourceKitRepresentable], key: SwiftDocKey, raw: File) -> [T] + static func parse(structure: [String: SourceKitRepresentable], raw: File) -> T? +} + +extension ElementParser { + static func parse(structure: [String: SourceKitRepresentable], key: SwiftDocKey, raw: File) -> [T] { + guard let substructures = structure[key.rawValue] as? [SourceKitRepresentable] else { + return [] + } + + return substructures + .compactMap { $0 as? [String: SourceKitRepresentable] } + .map { parse(structure: $0, raw: raw) } + .compactMap { $0 } + } +} diff --git a/Generator/Sources/Parser/EnumCaseParser.swift b/Generator/Sources/Parser/EnumCaseParser.swift new file mode 100644 index 0000000..93deeb0 --- /dev/null +++ b/Generator/Sources/Parser/EnumCaseParser.swift @@ -0,0 +1,75 @@ +// +// EnumCaseParser.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/08. +// + +import Core +import Foundation +import SourceKittenFramework + +struct EnumCaseParser: ElementParser { + static func parse(structure: [String: SourceKitRepresentable], raw: File) -> Enum.Case? { + guard (structure[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init) == .enumcase else { + return nil + } + + guard let substructure = (structure[SwiftDocKey.substructure.rawValue] as? [SourceKitRepresentable])?.first as? [String: SourceKitRepresentable] else { + return nil + } + + guard let name = substructure[SwiftDocKey.name.rawValue] as? String, + (substructure[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init) == .enumelement else { + return nil + } + + guard let offset = structure[SwiftDocKey.offset.rawValue] as? Int64, + let length = structure[SwiftDocKey.length.rawValue] as? Int64 else { + return nil + } + + let contents = raw.contents + let enumCase = String(contents[contents.index(contents.startIndex, offsetBy: Int(offset)).. Variable? { + let raws = raw.split(separator: ":") + switch raws.count { + case 2: + guard let name = raws.first.map(String.init)?.trimmingCharacters(in: .whitespacesAndNewlines) else { + return nil + } + + guard let typeName = raws.dropFirst().first.map(String.init)?.trimmingCharacters(in: .whitespacesAndNewlines) else { + return nil + } + + return Variable(name: name, typeName: TypeName(name: typeName)) + case 1: + guard let typeName = raws.first.map(String.init)?.trimmingCharacters(in: .whitespacesAndNewlines) else { + return nil + } + + return Variable(name: "", typeName: TypeName(name: typeName)) + default: + return nil + } + } +} diff --git a/Generator/Sources/Parser/EnumParser.swift b/Generator/Sources/Parser/EnumParser.swift new file mode 100644 index 0000000..92db060 --- /dev/null +++ b/Generator/Sources/Parser/EnumParser.swift @@ -0,0 +1,20 @@ +// +// EnumParser.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/08. +// + +import Core +import SourceKittenFramework + +struct EnumParser: TypeParser { + static func parse(structure: [String: SourceKitRepresentable], raw: File, name: String, generics: [Generic], conforms: [TypeName]) -> Enum? { + return Enum( + name: name, + generics: generics, + conforms: conforms, + cases: EnumCaseParser.parse(structure: structure, key: .substructure, raw: raw) + ) + } +} diff --git a/Generator/Sources/Parser/GenericParser.swift b/Generator/Sources/Parser/GenericParser.swift new file mode 100644 index 0000000..9571d74 --- /dev/null +++ b/Generator/Sources/Parser/GenericParser.swift @@ -0,0 +1,50 @@ +// +// GenericParser.swift +// Parser +// +// Created by Takeshi Ihara on 2018/06/11. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import Foundation +import SourceKittenFramework + +struct GenericParser { + static func parse(structure: [String : SourceKitRepresentable], raw: File) -> [Generic] { + guard let nameOffset = structure[SwiftDocKey.nameOffset.rawValue] as? Int64, + let bodyOffset = structure[SwiftDocKey.bodyOffset.rawValue] as? Int64 else { + return [] + } + + let contents = raw.contents + let name = String(contents[contents.index(contents.startIndex, offsetBy: Int(nameOffset)).."), + let match = regex.firstMatch(in: name, range: NSRange(location: 0, length: name.count)), + let range = Range(match.range, in: name) else { + return [] + } + + let generics = String(name[range]) + + return String(generics[generics.index(after: generics.startIndex).. Generic? { + let raws = raw.split(separator: ":") + guard let name = raws.first.map(String.init)?.trimmingCharacters(in: .whitespacesAndNewlines) else { + return nil + } + + let conforms = raws.dropFirst().first? + .split(separator: "&") + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .map(TypeName.init) ?? [] + + return Generic(name: name, conforms: conforms) + } +} diff --git a/Generator/Sources/Parser/ParseError.swift b/Generator/Sources/Parser/ParseError.swift new file mode 100644 index 0000000..1573454 --- /dev/null +++ b/Generator/Sources/Parser/ParseError.swift @@ -0,0 +1,12 @@ +// +// ParseError.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/07. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +public enum ParseError: Swift.Error { + case fileOpen + case parse +} diff --git a/Generator/Sources/Parser/Parser.swift b/Generator/Sources/Parser/Parser.swift new file mode 100644 index 0000000..303af1f --- /dev/null +++ b/Generator/Sources/Parser/Parser.swift @@ -0,0 +1,30 @@ +// +// Parser.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/06. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import SourceKittenFramework + +public struct Parser { + private let paths: [String] + + public init(paths: [String]) { + self.paths = paths + } + + public func run() throws -> [Type] { + return try paths + .map { path -> File in + guard let file = File(path: path) else { + throw ParseError.fileOpen + } + return file + } + .map { try TypesParser(file: $0).run() } + .reduce([]) { $0 + $1 } + } +} diff --git a/Generator/Sources/Parser/ProtocolParser.swift b/Generator/Sources/Parser/ProtocolParser.swift new file mode 100644 index 0000000..5697090 --- /dev/null +++ b/Generator/Sources/Parser/ProtocolParser.swift @@ -0,0 +1,16 @@ +// +// ProtocolParser.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/09. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import SourceKittenFramework + +struct ProtocolParser: TypeParser { + static func parse(structure: [String: SourceKitRepresentable], raw: File, name: String, generics: [Generic], conforms: [TypeName]) -> Protocol? { + return Protocol(name: name, generics: generics, conforms: conforms) + } +} diff --git a/Generator/Sources/Parser/StructParser.swift b/Generator/Sources/Parser/StructParser.swift new file mode 100644 index 0000000..3f244fd --- /dev/null +++ b/Generator/Sources/Parser/StructParser.swift @@ -0,0 +1,21 @@ +// +// StructParser.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/08. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import SourceKittenFramework + +struct StructParser: TypeParser { + static func parse(structure: [String: SourceKitRepresentable], raw: File, name: String, generics: [Generic], conforms: [TypeName]) -> Struct? { + return Struct( + name: name, + generics: generics, + conforms: conforms, + variables: VariableParser.parse(structure: structure, key: .substructure, raw: raw) + ) + } +} diff --git a/Generator/Sources/Parser/TypeParser.swift b/Generator/Sources/Parser/TypeParser.swift new file mode 100644 index 0000000..cf40d4c --- /dev/null +++ b/Generator/Sources/Parser/TypeParser.swift @@ -0,0 +1,30 @@ +// +// TypeParser.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/08. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import SourceKittenFramework + +protocol TypeParser { + associatedtype T: Type + static func parse(structure: [String: SourceKitRepresentable], raw: File, name: String, generics: [Generic], conforms: [TypeName]) -> T? + static func parse(structure: [String: SourceKitRepresentable], raw: File, parentName: String?) -> T? +} + +extension TypeParser { + static func parse(structure: [String: SourceKitRepresentable], raw: File, parentName: String?) -> T? { + guard let name = structure[SwiftDocKey.name.rawValue] as? String else { + return nil + } + + return parse(structure: structure, + raw: raw, + name: parentName.map({ "\($0).\(name)" }) ?? name, + generics: GenericParser.parse(structure: structure, raw: raw), + conforms: ConformParser.parse(structure: structure, key: .inheritedtypes, raw: raw)) + } +} diff --git a/Generator/Sources/Parser/TypesParser.swift b/Generator/Sources/Parser/TypesParser.swift new file mode 100644 index 0000000..3a20f1f --- /dev/null +++ b/Generator/Sources/Parser/TypesParser.swift @@ -0,0 +1,65 @@ +// +// TypesParser.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/07. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import SourceKittenFramework + +struct TypesParser { + private let file: File + + init(file: File) { + self.file = file + } + + func run() throws -> [Type] { + do { + let structure = try Structure(file: file).dictionary + return parse(structure: structure) + } catch { + throw ParseError.parse + } + } + + private func parse(structure: [String: SourceKitRepresentable], parentName: String? = nil) -> [Type] { + let types = [parse(structure: structure, parentName: parentName)].compactMap({ $0 }) + + guard let substructure = structure[SwiftDocKey.substructure.rawValue] as? [SourceKitRepresentable] else { + return types + } + + return substructure + .compactMap { $0 as? [String: SourceKitRepresentable] } + .map { parse(structure: $0, parentName: parseParentName(structure: structure, parentName: parentName)) } + .reduce(types) { $0 + $1 } + } + + private func parse(structure: [String: SourceKitRepresentable], parentName: String?) -> Type? { + guard let kind = (structure[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init) else { + return nil + } + + switch kind { + case .enum: + return EnumParser.parse(structure: structure, raw: file, parentName: parentName) + case .protocol: + return ProtocolParser.parse(structure: structure, raw: file, parentName: parentName) + case .struct: + return StructParser.parse(structure: structure, raw: file, parentName: parentName) + default: + return nil + } + } + + private func parseParentName(structure: [String: SourceKitRepresentable], parentName: String?) -> String? { + guard let name = structure[SwiftDocKey.name.rawValue] as? String, structure[SwiftDocKey.kind.rawValue] != nil else { + return nil + } + + return parentName.map({ "\($0).\(name)" }) ?? name + } +} diff --git a/Generator/Sources/Parser/VariableParser.swift b/Generator/Sources/Parser/VariableParser.swift new file mode 100644 index 0000000..4fd3fc0 --- /dev/null +++ b/Generator/Sources/Parser/VariableParser.swift @@ -0,0 +1,46 @@ +// +// VariableParser.swift +// Parser +// +// Created by Ihara Takeshi on 2018/06/07. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Core +import Foundation +import SourceKittenFramework + +struct VariableParser: ElementParser { + static func parse(structure: [String: SourceKitRepresentable], raw: File) -> Variable? { + guard let kind = (structure[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init), kind == .varInstance else { + return nil + } + + guard let name = structure[SwiftDocKey.name.rawValue] as? String, + let typeName = (structure[SwiftDocKey.typeName.rawValue] as? String).flatMap(TypeName.init) else { + return nil + } + + guard let offset = structure[SwiftDocKey.offset.rawValue] as? Int64, + let length = structure[SwiftDocKey.length.rawValue] as? Int64 else { + return nil + } + + let contents = raw.contents + let variable = String(contents[contents.index(contents.startIndex, offsetBy: Int(offset)).. Wall { + return .hang + } + } + """ + ) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } + + func testProtocol() { + let actual = try! CodeGenerator.run( + types: [ + Protocol( + name: "Climbable", + generics: [], + conforms: [] + ) + ], + by: .factory, + with: options + ) + + let expected = TemplateHelper.factory( + protocols: """ + """ + ) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } + + func testStruct() { + let actual = try! CodeGenerator.run( + types: [ + Struct( + name: "Climber", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "age", typeName: TypeName(name: "Int")) + ] + ) + ], + by: .factory, + with: options + ) + + let expected = TemplateHelper.factory( + structs: """ + extension Climber: Providable { + public static func provide() -> Climber { + return Climber( + name: String.provide(), + age: Int.provide() + ) + } + } + """ + ) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } + + // MARK: - Condition + + func testNested() { + let actual = try! CodeGenerator.run( + types: [ + Struct( + name: "Hold", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "type", typeName: TypeName(name: "Type")) + ] + ), + Enum( + name: "Hold.Type", + generics: [], + conforms: [], + cases: [ + Enum.Case(name: "bucket", variables: []), + Enum.Case(name: "pocket", variables: []), + Enum.Case(name: "sloper", variables: []), + Enum.Case(name: "tip", variables: []), + Enum.Case(name: "under", variables: []) + ] + ) + ], + by: .factory, + with: options + ) + + let expected = TemplateHelper.factory( + enums: """ + extension Hold.Type: Providable { + public static func provide() -> Hold.Type { + return .bucket + } + } + """, + structs: """ + extension Hold: Providable { + public static func provide() -> Hold { + return Hold( + name: String.provide(), + type: Type.provide() + ) + } + } + """ + ) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } + + func testConforms() { + let actual = try! CodeGenerator.run( + types: [ + Protocol( + name: "Climbable", + generics: [], + conforms: [] + ), + Protocol( + name: "Climbable2", + generics: [], + conforms: [TypeName(name: "Climbable")] + ), + Struct( + name: "Wall", + generics: [], + conforms: [TypeName(name: "Climbable2")], + variables: [] + ), + Struct( + name: "Hold", + generics: [], + conforms: [TypeName(name: "Climbable2")], + variables: [ + Variable(name: "name", typeName: TypeName(name: "Climbable3")) + ] + ) + ], + by: .factory, + with: options + ) + + let expected = TemplateHelper.factory( + protocols: """ + """, + structs: """ + extension Wall: Providable { + public static func provide() -> Wall { + return Wall( + ) + } + } + + extension Hold: Providable { + public static func provide() -> Hold { + return Hold( + name: Climbable3.provide() + ) + } + } + """ + ) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } + + func testGenerics() { + let actual = try! CodeGenerator.run( + types: [ + Struct( + name: "Wall", + generics: [ + Generic(name: "T", conforms: []), + Generic(name: "S", conforms: [TypeName(name: "Climbable")]) + ], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "angle", typeName: TypeName(name: "Float")) + ] + ) + ], + by: .factory, + with: options + ) + + let expected = TemplateHelper.factory( + structs: """ + extension Wall: Providable where T: Providable, S: Providable { + public static func provide() -> Wall { + return Wall( + name: String.provide(), + angle: Float.provide() + ) + } + } + """ + ) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } + + // MARK: - Enum Option + + func testAssociatedValues() { + let actual = try! CodeGenerator.run( + types: [ + Enum( + name: "Place", + generics: [], + conforms: [], + cases: [ + Enum.Case( + name: "other", + variables: [ + Variable(name: "", typeName: TypeName(name: "String")), + Variable(name: "", typeName: TypeName(name: "Float")) + ] + ) + ] + ) + ], + by: .factory, + with: options + ) + + let expected = TemplateHelper.factory( + enums: """ + extension Place: Providable { + public static func provide() -> Place { + return .other( + String.provide(), + Float.provide() + ) + } + } + """ + ) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } + + func testAssociatedValuesWithLabel() { + let actual = try! CodeGenerator.run( + types: [ + Enum( + name: "Place", + generics: [], + conforms: [], + cases: [ + Enum.Case( + name: "other", + variables: [ + Variable(name: "label1", typeName: TypeName(name: "String")), + Variable(name: "label2", typeName: TypeName(name: "Encodable & Decodable")), + ] + ), + ] + ) + ], + by: .factory, + with: options + ) + + let expected = TemplateHelper.factory( + enums: """ + extension Place: Providable { + public static func provide() -> Place { + return .other( + label1: String.provide(), + label2: Encodable.provide() + ) + } + } + """ + ) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } + + // MARK: - Lens + + // MARK: - Type + + func testLens() { + let actual = try! CodeGenerator.run( + types: [ + Struct( + name: "Climber", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "age", typeName: TypeName(name: "Int")) + ] + ) + ], + by: .lens, + with: options + ) + + let expected: String = TemplateHelper.lens(structs: """ + extension Climber { + static var _name: Lens { + return Lens( + getter: { $0.name }, + setter: { base, name in + Climber(name: name, age: base.age) + } + ) + } + static var _age: Lens { + return Lens( + getter: { $0.age }, + setter: { base, age in + Climber(name: base.name, age: age) + } + ) + } + } + """) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } + + func testGenericsLens() { + let actual = try! CodeGenerator.run( + types: [ + Struct( + name: "Climber", + generics: [ + Generic(name: "T", conforms: [TypeName(name: "Equatable")]) + ], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "age", typeName: TypeName(name: "T")) + ] + ) + ], + by: .lens, + with: options + ) + + let expected: String = TemplateHelper.lens(structs: """ + extension Climber { + static var _name: Lens { + return Lens( + getter: { $0.name }, + setter: { base, name in + Climber(name: name, age: base.age) + } + ) + } + static var _age: Lens { + return Lens( + getter: { $0.age }, + setter: { base, age in + Climber(name: base.name, age: age) + } + ) + } + } + """) + + XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) + } +} diff --git a/Generator/Tests/GeneratorTests/TemplateHelper.swift b/Generator/Tests/GeneratorTests/TemplateHelper.swift new file mode 100644 index 0000000..bb099ea --- /dev/null +++ b/Generator/Tests/GeneratorTests/TemplateHelper.swift @@ -0,0 +1,38 @@ +// +// TemplateHelper.swift +// GeneratorTests +// +// Created by Ihara Takeshi on 2018/06/12. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Foundation + +struct TemplateHelper { + static func factory(enums: String? = nil, protocols: String? = nil, structs: String? = nil) -> String { + return """ + // MARK: - Factory + + // MARK: - Enum + \(component(with: enums)) + // MARK: - Struct + \(component(with: structs)) + + """ + } + + static func lens(structs: String? = nil) -> String { + return """ + // MARK: - Lens + \(component(with: structs)) + + """ + } + + private static func component(with types: String?) -> String { + guard let types = types else { + return "" + } + return "\n\(types)\n" + } +} diff --git a/Generator/Tests/ParserTests/TypeParserTests.swift b/Generator/Tests/ParserTests/TypeParserTests.swift new file mode 100644 index 0000000..b2d4859 --- /dev/null +++ b/Generator/Tests/ParserTests/TypeParserTests.swift @@ -0,0 +1,405 @@ +// +// TypeParserTests.swift +// ParserTests +// +// Created by Ihara Takeshi on 2018/06/08. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import MirrorDiffKit +import XCTest +@testable import Core +@testable import Parser +@testable import SourceKittenFramework + +class TypeParserTests: XCTestCase { + + // MARK: - Type + + func testEnum() { + let file = File(contents: """ + enum Wall { + case hang + case vertical + case slab + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Enum( + name: "Wall", + generics: [], + conforms: [], + cases: [ + Enum.Case(name: "hang", variables: []), + Enum.Case(name: "vertical", variables: []), + Enum.Case(name: "slab", variables: []) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + func testProtocol() { + let file = File(contents: """ + protocol Climbable { + var condition: String { get } + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Protocol( + name: "Climbable", + generics: [], + conforms: [] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + func testStruct() { + let file = File(contents: """ + struct Climber { + let name: String + let age: Int + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Struct( + name: "Climber", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "age", typeName: TypeName(name: "Int")) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + // MARK: - Condition + + func testComputedProperty() { + let file = File(contents: """ + struct Wall { + let name: String + let angle: Float + + var isSlab: Bool { + return angle < 90.0 + } + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Struct( + name: "Wall", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "angle", typeName: TypeName(name: "Float")) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + func testExtension() { + let file = File(contents: """ + struct Hold { + let name: String + let type: Type + } + + extension Hold { + enum Type { + case bucket + case pocket + case sloper + case tip + case under + } + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Struct( + name: "Hold", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "type", typeName: TypeName(name: "Type")) + ] + ), + Enum( + name: "Hold.Type", + generics: [], + conforms: [], + cases: [ + Enum.Case(name: "bucket", variables: []), + Enum.Case(name: "pocket", variables: []), + Enum.Case(name: "sloper", variables: []), + Enum.Case(name: "tip", variables: []), + Enum.Case(name: "under", variables: []) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + func testNested() { + let file = File(contents: """ + struct Climbing { + struct Hold { + let name: String + let type: Type + + enum Type { + case bucket + case pocket + case sloper + case tip + case under + } + } + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Struct( + name: "Climbing", + generics: [], + conforms: [], + variables: [] + ), + Struct( + name: "Climbing.Hold", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "type", typeName: TypeName(name: "Type")) + ] + ), + Enum( + name: "Climbing.Hold.Type", + generics: [], + conforms: [], + cases: [ + Enum.Case(name: "bucket", variables: []), + Enum.Case(name: "pocket", variables: []), + Enum.Case(name: "sloper", variables: []), + Enum.Case(name: "tip", variables: []), + Enum.Case(name: "under", variables: []) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + func testGenerics() { + let file = File(contents: """ + struct Climber { + let name: T + let age: U + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Struct( + name: "Climber", + generics: [ + Generic(name: "T", conforms: [TypeName(name: "Equatable")]), + Generic(name: "U", conforms: [TypeName(name: "Encodable"), TypeName(name: "Decodable")]) + ], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "T")), + Variable(name: "age", typeName: TypeName(name: "U")) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + // MARK: - Enum Option + + func testAssociatedValues() { + let file = File(contents: """ + enum Place { + case indoor + case outdoor + case other(String) + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Enum( + name: "Place", + generics: [], + conforms: [], + cases: [ + Enum.Case(name: "indoor", variables: []), + Enum.Case(name: "outdoor", variables: []), + Enum.Case(name: "other", variables: [Variable(name: "", typeName: TypeName(name: "String"))]) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + // MARK: - Protocol Option + + func testConforms() { + let file = File(contents: """ + protocol Climbable { + var condition: String { get } + } + + struct Wall: Climbable { + let name: String + let angle: Float + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Protocol( + name: "Climbable", + generics: [], + conforms: [] + ), + Struct( + name: "Wall", + generics: [], + conforms: [TypeName(name: "Climbable")], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "angle", typeName: TypeName(name: "Float")) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + // MARK: - Struct Option + + func testImmutableDefaultValue() { + let file = File(contents: """ + struct Climber { + let name: String + let age: Int = 26 + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Struct( + name: "Climber", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + func testImmutableClosure() { + let file = File(contents: """ + struct Climber { + let name: String + let age: Int = { + return 26 + }() + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Struct( + name: "Climber", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + func testMutableDefaultValue() { + let file = File(contents: """ + struct Climber { + let name: String + var age: Int = 26 + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Struct( + name: "Climber", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "age", typeName: TypeName(name: "Int")) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } + + func testMutableClosure() { + let file = File(contents: """ + struct Climber { + let name: String + var age: Int = { + return 26 + }() + } + """) + let actual = try! TypesParser(file: file).run() + + let expected: [Type] = [ + Struct( + name: "Climber", + generics: [], + conforms: [], + variables: [ + Variable(name: "name", typeName: TypeName(name: "String")), + Variable(name: "age", typeName: TypeName(name: "Int")) + ] + ) + ] + + XCTAssert(actual =~ expected, diff(between: actual, and: expected)) + } +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..7e8e15f --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +Copyright (c) 2018 Takeshi Ihara + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3ebc652 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +.PHONY : lint release + +lint: + pod lib lint --no-clean --allow-warnings + +release: + pod trunk push FactoryProvider.podspec + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf29d46 --- /dev/null +++ b/README.md @@ -0,0 +1,143 @@ +# :factory: FactoryProvider :factory: + +[![Build Status](https://travis-ci.com/Nonchalant/FactoryProvider.svg?branch=master)](https://travis-ci.com/Nonchalant/FactoryProvider) +[![Version](http://img.shields.io/cocoapods/v/FactoryProvider.svg?style=flat)](http://cocoadocs.org/pods/FactoryProvider) +[![Platform](http://img.shields.io/cocoapods/p/FactoryProvider.svg?style=flat)](http://cocoadocs.org/pods/FactoryProvider) +[![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/Nonchalant/FactoryProvider/master/LICENSE.md) +[![GitHub release](https://img.shields.io/github/release/Nonchalant/FactoryProvider.svg)](https://github.com/Nonchalant/FactoryProvider/releases) +![Xcode](https://img.shields.io/badge/Xcode-9.4-brightgreen.svg) +![Swift](https://img.shields.io/badge/Swift-4.1-brightgreen.svg) +[![Swift Package Manager](https://img.shields.io/badge/Swift%20Package%20Manager-4.0.0-brightgreen.svg)](https://github.com/apple/swift-package-manager) + +Generate boilerplate of factory Swift framework. + + +## Requirements + +FactoryProvider works on the following platforms: + +- **iOS 8+** +- **Mac OSX 10.9+** +- **watchOS 2+** +- **tvOS 9+** + + +## Supports + +Struct, Enum + + +## FactoryProvider + +### 1. Installation + +#### CocoaPods + +FactoryProvider runtime is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your test target in your Podfile: + +```Ruby +pod "FactoryProvider" +``` + +And add the following `Run script` build phase to your test target's `Build Phases`: + +```Bash +"${PODS_ROOT}/FactoryProvider/generate" \ + "${PROJECT_DIR}/${PROJECT_NAME}"/Input1/**/*.swift \ + "${PROJECT_DIR}/${PROJECT_NAME}"/Input2/**/*.swift \ + --exclude "${PROJECT_DIR}/${PROJECT_NAME}/Input2/InputFile.swift" \ + --testable "$PROJECT_NAME" \ + --output "$PROJECT_DIR/${PROJECT_NAME}Tests/Factories.generated.swift" +``` + +After running once, locate `Factories.generated.swift` and drag it into your Xcode test target group. + +#### --include + +path of files to generate + +#### --exclude + +path of files not to generate + +#### --testable + +testable target + +#### --output + +path of generated file + +### 2. Usage + +You can get a instance to call `.provide()`. Each properties are set to default value. + +```swift +struct Climber { + let name: String + let age: Int +} + +let climber = Climber.provide() +``` + +### 3. Lens + +`.provide()` provides fixed instance. You can modify each property by Lens. + +#### Get + +```swift +let name = Climber._name.get(Climber.provide()) +// "" +``` + +#### Set + +```swift +let climber = Climber._name.set(Climber.provide(), "Climber") +// Climber(name: "Climber", age: 0) +``` + +#### Modify + +```swift +let climber1 = Climber._name.set(Climber.provide(), "Climber") +// Climber(name: "Climber", age: 0) + +let climber = Climber._name.modify(climber1, f: { $0 + $0 }) +// Climber(name: "ClimberClimber", age: 0) +``` + +#### Compose + +```swift +struct Climber { + let id: Id + let name: String + + struct Id { + let value: String + } +} + +let climber1 = Climber.provide() +// Climber(id: Id(value: ""), name: "") + +let climber2 = Climber._id.compose(other: Climber.Id._value).set(climber1, "id") +// Climber(id: Id(value: "id"), name: "") +``` + + +## Libraries + +* [Commander](https://github.com/kylef/Commander) +* [PathKit](https://github.com/kylef/PathKit) +* [SourceKitten](https://github.com/jpsim/SourceKitten) +* [StencilSwiftKit](https://github.com/SwiftGen/StencilSwiftKit) +* [MirrorDiffKit](https://github.com/Kuniwak/MirrorDiffKit) + + +## License + +FactoryProvider is available under the [MIT License](LICENSE). diff --git a/Source/Lens.swift b/Source/Lens.swift new file mode 100644 index 0000000..bd1f374 --- /dev/null +++ b/Source/Lens.swift @@ -0,0 +1,42 @@ +// +// Lens.swift +// FactoryProvider +// +// Created by Ihara Takeshi on 2018/06/11. +// Copyright © 2018 Nonchalant. All rights reserved. +// + +import Foundation + +public struct Lens { + private let getter: (A) -> B + private let setter: (A, B) -> A + + public init(getter: @escaping (A) -> B, setter: @escaping (A, B) -> A) { + self.getter = getter + self.setter = setter + } + + public func get(_ from: A) -> B { + return getter(from) + } + + public func set(_ from: A, _ to: B) -> A { + return setter(from, to) + } + + public func modify(_ from: A, f: (B) -> B) -> A { + return set(from, f(get(from))) + } + + public func compose(other: Lens) -> Lens { + return Lens( + getter: { (a: A) -> C in + other.get(self.get(a)) + }, + setter: { (a: A, c: C) -> A in + self.set(a, other.set(self.get(a), c)) + } + ) + } +} diff --git a/Source/Providable.swift b/Source/Providable.swift new file mode 100644 index 0000000..051495a --- /dev/null +++ b/Source/Providable.swift @@ -0,0 +1,139 @@ +// +// Providable.swift +// FactoryProvider +// +// Created by Takeshi Ihara on 2018/06/06. +// Copyright © 2018年 Nonchalant. All rights reserved. +// + +import Foundation + +public protocol Providable { + static func provide() -> Self +} + +extension Array: Providable where Element: Providable { + public static func provide() -> Array { + return [Element.provide()] + } +} + +extension Bool: Providable { + public static func provide() -> Bool { + return false + } +} + +extension Character: Providable { + public static func provide() -> Character { + return Character(String.provide()) + } +} + +extension Data: Providable { + public static func provide() -> Data { + return Data() + } +} + +extension Date: Providable { + public static func provide() -> Date { + return Date(timeIntervalSince1970: 0) + } +} + +extension Dictionary: Providable where Key: Providable, Value: Providable { + public static func provide() -> Dictionary { + return [Key.provide(): Value.provide()] + } +} + +extension Double: Providable { + public static func provide() -> Double { + return 0 + } +} + +extension Float: Providable { + public static func provide() -> Float { + return 0 + } +} + +extension Int: Providable { + public static func provide() -> Int { + return 0 + } +} + +extension Int8: Providable { + public static func provide() -> Int8 { + return 0 + } +} + +extension Int16: Providable { + public static func provide() -> Int16 { + return 0 + } +} + +extension Int32: Providable { + public static func provide() -> Int32 { + return 0 + } +} + +extension Int64: Providable { + public static func provide() -> Int64 { + return 0 + } +} + +extension Optional: Providable where Wrapped: Providable { + public static func provide() -> Optional { + return .some(Wrapped.provide()) + } +} + +extension String: Providable { + public static func provide() -> String { + return "" + } +} + +extension UInt: Providable { + public static func provide() -> UInt { + return 0 + } +} + +extension UInt8: Providable { + public static func provide() -> UInt8 { + return 0 + } +} + +extension UInt16: Providable { + public static func provide() -> UInt16 { + return 0 + } +} + +extension UInt32: Providable { + public static func provide() -> UInt32 { + return 0 + } +} + +extension UInt64: Providable { + public static func provide() -> UInt64 { + return 0 + } +} + +extension URL: Providable { + public static func provide() -> URL { + return URL(string: "https://github.com/Nonchalant/FactoryProvider")! + } +} diff --git a/Source/Supporting Files/FactoryFactory.h b/Source/Supporting Files/FactoryFactory.h new file mode 100644 index 0000000..6d01a4e --- /dev/null +++ b/Source/Supporting Files/FactoryFactory.h @@ -0,0 +1,19 @@ +// +// FactoryProvider.h +// FactoryProvider +// +// Created by Takeshi Ihara on 2018/06/06. +// Copyright © 2018年 Nonchalant. All rights reserved. +// + +#import + +//! Project version number for FactoryProvider. +FOUNDATION_EXPORT double FactoryProviderVersionNumber; + +//! Project version string for FactoryProvider. +FOUNDATION_EXPORT const unsigned char FactoryProviderVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Source/Supporting Files/Info.plist b/Source/Supporting Files/Info.plist new file mode 100644 index 0000000..1007fd9 --- /dev/null +++ b/Source/Supporting Files/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + +