├── .gitignore ├── .swift-version ├── .travis.yml ├── FactoryProvider.podspec ├── FactoryProvider.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Generator ├── .gitignore ├── Makefile ├── Package.resolved ├── Package.swift ├── Sources │ ├── Core │ │ ├── Element.swift │ │ ├── Enum.swift │ │ ├── EnumCase.swift │ │ ├── Generic.swift │ │ ├── Options.swift │ │ ├── Protocol.swift │ │ ├── Struct.swift │ │ ├── Type.swift │ │ ├── TypeName.swift │ │ ├── Variable.swift │ │ └── Version.swift │ ├── FactoryGenerator │ │ └── main.swift │ ├── Generator │ │ ├── Arguments.swift │ │ ├── CodeGenerator.swift │ │ ├── FileGenerator.swift │ │ ├── GenerateError.swift │ │ ├── Generator.swift │ │ ├── Template.swift │ │ └── Types.swift │ └── Parser │ │ ├── ChildParser.swift │ │ ├── ConformParser.swift │ │ ├── ElementParser.swift │ │ ├── EnumCaseParser.swift │ │ ├── EnumParser.swift │ │ ├── GenericParser.swift │ │ ├── ParseError.swift │ │ ├── Parser.swift │ │ ├── ProtocolParser.swift │ │ ├── StructParser.swift │ │ ├── TypeParser.swift │ │ ├── TypesParser.swift │ │ ├── Variable+TypeName.swift │ │ └── VariableParser.swift └── Tests │ ├── GeneratorTests │ ├── CodeGeneratorTests.swift │ └── TemplateHelper.swift │ └── ParserTests │ └── TypeParserTests.swift ├── LICENSE.md ├── Makefile ├── README.md └── Source ├── Factory+Primitive.swift ├── Factory.swift ├── Lens.swift ├── LensOver.swift └── Supporting Files ├── FactoryProvider.h └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | 3 | .build/ 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | # FactoryProvider 22 | 23 | generate -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.1 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode10 3 | 4 | script: 5 | - cd Generator && swift test -------------------------------------------------------------------------------- /FactoryProvider.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "FactoryProvider" 3 | s.version = "0.5.2" 4 | s.summary = "FactoryProvider - generate boilerplate of factory Swift framework." 5 | s.description = <<-DESC 6 | FactoryProvider is a framework to generate boilerplate of factory with an easy to write TestCase. 7 | It generates factories automatically to enable this functionality. 8 | DESC 9 | 10 | s.homepage = "https://github.com/Nonchalant/FactoryProvider" 11 | s.license = 'MIT' 12 | s.author = { "Takeshi Ihara" => "afrontier829@gmail.com" } 13 | s.source = { 14 | :git => "https://github.com/Nonchalant/FactoryProvider.git", 15 | :tag => s.version.to_s 16 | } 17 | 18 | s.ios.deployment_target = '8.0' 19 | # s.osx.deployment_target = '10.9' 20 | s.watchos.deployment_target = '2.0' 21 | s.tvos.deployment_target = '9.0' 22 | s.source_files = ['Source/**/*.swift'] 23 | generator_name = 'generate' 24 | s.preserve_paths = ['Generator/**/*', generator_name] 25 | s.prepare_command = <<-CMD 26 | curl -Lo #{generator_name} https://github.com/Nonchalant/FactoryProvider/releases/download/#{s.version}/#{generator_name} 27 | chmod +x #{generator_name} 28 | CMD 29 | s.frameworks = 'Foundation' 30 | s.requires_arc = true 31 | s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO', 'SWIFT_REFLECTION_METADATA_LEVEL' => 'none' } 32 | end -------------------------------------------------------------------------------- /FactoryProvider.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 244D1D5E20DB7D280058FCC9 /* FactoryProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 244D1D5D20DB7D280058FCC9 /* FactoryProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 245D5E1A20C6FCC4009DC9EE /* Factory+Primitive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 245D5E1920C6FCC4009DC9EE /* Factory+Primitive.swift */; }; 12 | 5F3E4332216F1015002B4F9B /* Lens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F3E4331216F1015002B4F9B /* Lens.swift */; }; 13 | 604121FE20CDAFBF00FCBE7A /* LensOver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604121FD20CDAFBF00FCBE7A /* LensOver.swift */; }; 14 | 60B30AC6214E7C2600E0B844 /* Factory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60B30AC5214E7C2600E0B844 /* Factory.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 244D1D5D20DB7D280058FCC9 /* FactoryProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FactoryProvider.h; sourceTree = ""; }; 19 | 245D5E0D20C6FC0F009DC9EE /* FactoryProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FactoryProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 245D5E1120C6FC0F009DC9EE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 21 | 245D5E1920C6FCC4009DC9EE /* Factory+Primitive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Factory+Primitive.swift"; sourceTree = ""; }; 22 | 5F3E4331216F1015002B4F9B /* Lens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lens.swift; sourceTree = ""; }; 23 | 604121FD20CDAFBF00FCBE7A /* LensOver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LensOver.swift; sourceTree = ""; }; 24 | 60B30AC5214E7C2600E0B844 /* Factory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Factory.swift; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXFrameworksBuildPhase section */ 28 | 245D5E0920C6FC0F009DC9EE /* Frameworks */ = { 29 | isa = PBXFrameworksBuildPhase; 30 | buildActionMask = 2147483647; 31 | files = ( 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXFrameworksBuildPhase section */ 36 | 37 | /* Begin PBXGroup section */ 38 | 245D5E0320C6FC0F009DC9EE = { 39 | isa = PBXGroup; 40 | children = ( 41 | 245D5E0F20C6FC0F009DC9EE /* Source */, 42 | 245D5E0E20C6FC0F009DC9EE /* Products */, 43 | ); 44 | sourceTree = ""; 45 | }; 46 | 245D5E0E20C6FC0F009DC9EE /* Products */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | 245D5E0D20C6FC0F009DC9EE /* FactoryProvider.framework */, 50 | ); 51 | name = Products; 52 | sourceTree = ""; 53 | }; 54 | 245D5E0F20C6FC0F009DC9EE /* Source */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | 60B30AC5214E7C2600E0B844 /* Factory.swift */, 58 | 245D5E1920C6FCC4009DC9EE /* Factory+Primitive.swift */, 59 | 5F3E4331216F1015002B4F9B /* Lens.swift */, 60 | 604121FD20CDAFBF00FCBE7A /* LensOver.swift */, 61 | 245D5E1820C6FC9D009DC9EE /* Supporting Files */, 62 | ); 63 | path = Source; 64 | sourceTree = ""; 65 | }; 66 | 245D5E1820C6FC9D009DC9EE /* Supporting Files */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 244D1D5D20DB7D280058FCC9 /* FactoryProvider.h */, 70 | 245D5E1120C6FC0F009DC9EE /* Info.plist */, 71 | ); 72 | path = "Supporting Files"; 73 | sourceTree = ""; 74 | }; 75 | /* End PBXGroup section */ 76 | 77 | /* Begin PBXHeadersBuildPhase section */ 78 | 245D5E0A20C6FC0F009DC9EE /* Headers */ = { 79 | isa = PBXHeadersBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 244D1D5E20DB7D280058FCC9 /* FactoryProvider.h in Headers */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXHeadersBuildPhase section */ 87 | 88 | /* Begin PBXNativeTarget section */ 89 | 245D5E0C20C6FC0F009DC9EE /* FactoryProvider */ = { 90 | isa = PBXNativeTarget; 91 | buildConfigurationList = 245D5E1520C6FC0F009DC9EE /* Build configuration list for PBXNativeTarget "FactoryProvider" */; 92 | buildPhases = ( 93 | 245D5E0820C6FC0F009DC9EE /* Sources */, 94 | 245D5E0920C6FC0F009DC9EE /* Frameworks */, 95 | 245D5E0A20C6FC0F009DC9EE /* Headers */, 96 | 245D5E0B20C6FC0F009DC9EE /* Resources */, 97 | ); 98 | buildRules = ( 99 | ); 100 | dependencies = ( 101 | ); 102 | name = FactoryProvider; 103 | productName = FactoryProvider; 104 | productReference = 245D5E0D20C6FC0F009DC9EE /* FactoryProvider.framework */; 105 | productType = "com.apple.product-type.framework"; 106 | }; 107 | /* End PBXNativeTarget section */ 108 | 109 | /* Begin PBXProject section */ 110 | 245D5E0420C6FC0F009DC9EE /* Project object */ = { 111 | isa = PBXProject; 112 | attributes = { 113 | LastUpgradeCheck = 0940; 114 | ORGANIZATIONNAME = Nonchalant; 115 | TargetAttributes = { 116 | 245D5E0C20C6FC0F009DC9EE = { 117 | CreatedOnToolsVersion = 9.4; 118 | LastSwiftMigration = 0940; 119 | }; 120 | }; 121 | }; 122 | buildConfigurationList = 245D5E0720C6FC0F009DC9EE /* Build configuration list for PBXProject "FactoryProvider" */; 123 | compatibilityVersion = "Xcode 9.3"; 124 | developmentRegion = en; 125 | hasScannedForEncodings = 0; 126 | knownRegions = ( 127 | en, 128 | ); 129 | mainGroup = 245D5E0320C6FC0F009DC9EE; 130 | productRefGroup = 245D5E0E20C6FC0F009DC9EE /* Products */; 131 | projectDirPath = ""; 132 | projectRoot = ""; 133 | targets = ( 134 | 245D5E0C20C6FC0F009DC9EE /* FactoryProvider */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXResourcesBuildPhase section */ 140 | 245D5E0B20C6FC0F009DC9EE /* Resources */ = { 141 | isa = PBXResourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | /* End PBXResourcesBuildPhase section */ 148 | 149 | /* Begin PBXSourcesBuildPhase section */ 150 | 245D5E0820C6FC0F009DC9EE /* Sources */ = { 151 | isa = PBXSourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | 245D5E1A20C6FCC4009DC9EE /* Factory+Primitive.swift in Sources */, 155 | 5F3E4332216F1015002B4F9B /* Lens.swift in Sources */, 156 | 604121FE20CDAFBF00FCBE7A /* LensOver.swift in Sources */, 157 | 60B30AC6214E7C2600E0B844 /* Factory.swift in Sources */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXSourcesBuildPhase section */ 162 | 163 | /* Begin XCBuildConfiguration section */ 164 | 245D5E1320C6FC0F009DC9EE /* Debug */ = { 165 | isa = XCBuildConfiguration; 166 | buildSettings = { 167 | ALWAYS_SEARCH_USER_PATHS = NO; 168 | CLANG_ANALYZER_NONNULL = YES; 169 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 170 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 171 | CLANG_CXX_LIBRARY = "libc++"; 172 | CLANG_ENABLE_MODULES = YES; 173 | CLANG_ENABLE_OBJC_ARC = YES; 174 | CLANG_ENABLE_OBJC_WEAK = YES; 175 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 176 | CLANG_WARN_BOOL_CONVERSION = YES; 177 | CLANG_WARN_COMMA = YES; 178 | CLANG_WARN_CONSTANT_CONVERSION = YES; 179 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 182 | CLANG_WARN_EMPTY_BODY = YES; 183 | CLANG_WARN_ENUM_CONVERSION = YES; 184 | CLANG_WARN_INFINITE_RECURSION = YES; 185 | CLANG_WARN_INT_CONVERSION = YES; 186 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 187 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 188 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 190 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 191 | CLANG_WARN_STRICT_PROTOTYPES = YES; 192 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 193 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 194 | CLANG_WARN_UNREACHABLE_CODE = YES; 195 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 196 | CODE_SIGN_IDENTITY = "iPhone Developer"; 197 | COPY_PHASE_STRIP = NO; 198 | CURRENT_PROJECT_VERSION = 1; 199 | DEBUG_INFORMATION_FORMAT = dwarf; 200 | ENABLE_STRICT_OBJC_MSGSEND = YES; 201 | ENABLE_TESTABILITY = YES; 202 | GCC_C_LANGUAGE_STANDARD = gnu11; 203 | GCC_DYNAMIC_NO_PIC = NO; 204 | GCC_NO_COMMON_BLOCKS = YES; 205 | GCC_OPTIMIZATION_LEVEL = 0; 206 | GCC_PREPROCESSOR_DEFINITIONS = ( 207 | "DEBUG=1", 208 | "$(inherited)", 209 | ); 210 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 211 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 212 | GCC_WARN_UNDECLARED_SELECTOR = YES; 213 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 214 | GCC_WARN_UNUSED_FUNCTION = YES; 215 | GCC_WARN_UNUSED_VARIABLE = YES; 216 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 217 | MTL_ENABLE_DEBUG_INFO = YES; 218 | ONLY_ACTIVE_ARCH = YES; 219 | SDKROOT = iphoneos; 220 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 221 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 222 | VERSIONING_SYSTEM = "apple-generic"; 223 | VERSION_INFO_PREFIX = ""; 224 | }; 225 | name = Debug; 226 | }; 227 | 245D5E1420C6FC0F009DC9EE /* Release */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | ALWAYS_SEARCH_USER_PATHS = NO; 231 | CLANG_ANALYZER_NONNULL = YES; 232 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 234 | CLANG_CXX_LIBRARY = "libc++"; 235 | CLANG_ENABLE_MODULES = YES; 236 | CLANG_ENABLE_OBJC_ARC = YES; 237 | CLANG_ENABLE_OBJC_WEAK = YES; 238 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_COMMA = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 243 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 244 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 245 | CLANG_WARN_EMPTY_BODY = YES; 246 | CLANG_WARN_ENUM_CONVERSION = YES; 247 | CLANG_WARN_INFINITE_RECURSION = YES; 248 | CLANG_WARN_INT_CONVERSION = YES; 249 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 251 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 253 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 254 | CLANG_WARN_STRICT_PROTOTYPES = YES; 255 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 256 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 257 | CLANG_WARN_UNREACHABLE_CODE = YES; 258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 259 | CODE_SIGN_IDENTITY = "iPhone Developer"; 260 | COPY_PHASE_STRIP = NO; 261 | CURRENT_PROJECT_VERSION = 1; 262 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 263 | ENABLE_NS_ASSERTIONS = NO; 264 | ENABLE_STRICT_OBJC_MSGSEND = YES; 265 | GCC_C_LANGUAGE_STANDARD = gnu11; 266 | GCC_NO_COMMON_BLOCKS = YES; 267 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 268 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 269 | GCC_WARN_UNDECLARED_SELECTOR = YES; 270 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 271 | GCC_WARN_UNUSED_FUNCTION = YES; 272 | GCC_WARN_UNUSED_VARIABLE = YES; 273 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 274 | MTL_ENABLE_DEBUG_INFO = NO; 275 | SDKROOT = iphoneos; 276 | SWIFT_COMPILATION_MODE = wholemodule; 277 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 278 | VALIDATE_PRODUCT = YES; 279 | VERSIONING_SYSTEM = "apple-generic"; 280 | VERSION_INFO_PREFIX = ""; 281 | }; 282 | name = Release; 283 | }; 284 | 245D5E1620C6FC0F009DC9EE /* Debug */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | CLANG_ENABLE_MODULES = YES; 288 | CODE_SIGN_IDENTITY = ""; 289 | CODE_SIGN_STYLE = Automatic; 290 | DEFINES_MODULE = YES; 291 | DYLIB_COMPATIBILITY_VERSION = 1; 292 | DYLIB_CURRENT_VERSION = 1; 293 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 294 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 295 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 296 | LD_RUNPATH_SEARCH_PATHS = ( 297 | "$(inherited)", 298 | "@executable_path/Frameworks", 299 | "@loader_path/Frameworks", 300 | ); 301 | PRODUCT_BUNDLE_IDENTIFIER = com.nonchalant.FactoryProvider; 302 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 303 | SKIP_INSTALL = YES; 304 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 305 | SWIFT_VERSION = 4.0; 306 | TARGETED_DEVICE_FAMILY = "1,2"; 307 | }; 308 | name = Debug; 309 | }; 310 | 245D5E1720C6FC0F009DC9EE /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | CLANG_ENABLE_MODULES = YES; 314 | CODE_SIGN_IDENTITY = ""; 315 | CODE_SIGN_STYLE = Automatic; 316 | DEFINES_MODULE = YES; 317 | DYLIB_COMPATIBILITY_VERSION = 1; 318 | DYLIB_CURRENT_VERSION = 1; 319 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 320 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 321 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 322 | LD_RUNPATH_SEARCH_PATHS = ( 323 | "$(inherited)", 324 | "@executable_path/Frameworks", 325 | "@loader_path/Frameworks", 326 | ); 327 | PRODUCT_BUNDLE_IDENTIFIER = com.nonchalant.FactoryProvider; 328 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 329 | SKIP_INSTALL = YES; 330 | SWIFT_VERSION = 4.0; 331 | TARGETED_DEVICE_FAMILY = "1,2"; 332 | }; 333 | name = Release; 334 | }; 335 | /* End XCBuildConfiguration section */ 336 | 337 | /* Begin XCConfigurationList section */ 338 | 245D5E0720C6FC0F009DC9EE /* Build configuration list for PBXProject "FactoryProvider" */ = { 339 | isa = XCConfigurationList; 340 | buildConfigurations = ( 341 | 245D5E1320C6FC0F009DC9EE /* Debug */, 342 | 245D5E1420C6FC0F009DC9EE /* Release */, 343 | ); 344 | defaultConfigurationIsVisible = 0; 345 | defaultConfigurationName = Release; 346 | }; 347 | 245D5E1520C6FC0F009DC9EE /* Build configuration list for PBXNativeTarget "FactoryProvider" */ = { 348 | isa = XCConfigurationList; 349 | buildConfigurations = ( 350 | 245D5E1620C6FC0F009DC9EE /* Debug */, 351 | 245D5E1720C6FC0F009DC9EE /* Release */, 352 | ); 353 | defaultConfigurationIsVisible = 0; 354 | defaultConfigurationName = Release; 355 | }; 356 | /* End XCConfigurationList section */ 357 | }; 358 | rootObject = 245D5E0420C6FC0F009DC9EE /* Project object */; 359 | } 360 | -------------------------------------------------------------------------------- /FactoryProvider.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FactoryProvider.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Generator/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /.config.yml 4 | /Packages 5 | /*.xcodeproj -------------------------------------------------------------------------------- /Generator/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : clean build test release xcodeproj 2 | 3 | default: clean build 4 | 5 | clean: 6 | swift package clean 7 | rm -rf .build ./FactoryGenerator.xcodeproj 8 | 9 | build: 10 | swift build 11 | 12 | test: 13 | swift test 14 | 15 | release: 16 | swift build -c release -Xswiftc -static-stdlib 17 | 18 | xcodeproj: 19 | swift package generate-xcodeproj 20 | -------------------------------------------------------------------------------- /Generator/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Commandant", 6 | "repositoryURL": "https://github.com/Carthage/Commandant.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "07cad52573bad19d95844035bf0b25acddf6b0f6", 10 | "version": "0.15.0" 11 | } 12 | }, 13 | { 14 | "package": "Commander", 15 | "repositoryURL": "https://github.com/kylef/Commander", 16 | "state": { 17 | "branch": null, 18 | "revision": "e5b50ad7b2e91eeb828393e89b03577b16be7db9", 19 | "version": "0.8.0" 20 | } 21 | }, 22 | { 23 | "package": "MirrorDiffKit", 24 | "repositoryURL": "https://github.com/Kuniwak/MirrorDiffKit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "b49d628caa3232890241204a3181849d5ffa6b71", 28 | "version": "3.1.0" 29 | } 30 | }, 31 | { 32 | "package": "Nimble", 33 | "repositoryURL": "https://github.com/Quick/Nimble.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "cd6dfb86f496fcd96ce0bc6da962cd936bf41903", 37 | "version": "7.3.1" 38 | } 39 | }, 40 | { 41 | "package": "PathKit", 42 | "repositoryURL": "https://github.com/kylef/PathKit.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "fa81fa9e3a9f59645159c4ea45c0c46ee6558f71", 46 | "version": "0.9.1" 47 | } 48 | }, 49 | { 50 | "package": "Quick", 51 | "repositoryURL": "https://github.com/Quick/Quick.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "5fbf13871d185526993130c3a1fad0b70bfe37ce", 55 | "version": "1.3.2" 56 | } 57 | }, 58 | { 59 | "package": "Result", 60 | "repositoryURL": "https://github.com/antitypical/Result.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "8fc088dcf72802801efeecba76ea8fb041fb773d", 64 | "version": "4.0.0" 65 | } 66 | }, 67 | { 68 | "package": "SourceKitten", 69 | "repositoryURL": "https://github.com/jpsim/SourceKitten.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "4be914be6fa49cd30b1e7ef5d32d06c037d8f469", 73 | "version": "0.21.2" 74 | } 75 | }, 76 | { 77 | "package": "Spectre", 78 | "repositoryURL": "https://github.com/kylef/Spectre.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "e34d5687e1e9d865e3527dd58bc2f7464ef6d936", 82 | "version": "0.8.0" 83 | } 84 | }, 85 | { 86 | "package": "Stencil", 87 | "repositoryURL": "https://github.com/kylef/Stencil.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "b476e50f89577f5848e8013dbf0a850abac892aa", 91 | "version": "0.12.1" 92 | } 93 | }, 94 | { 95 | "package": "StencilSwiftKit", 96 | "repositoryURL": "https://github.com/SwiftGen/StencilSwiftKit.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "17e6ae91838afb90ab873194e45fa5b01731e322", 100 | "version": "2.6.0" 101 | } 102 | }, 103 | { 104 | "package": "SWXMLHash", 105 | "repositoryURL": "https://github.com/drmohundro/SWXMLHash.git", 106 | "state": { 107 | "branch": null, 108 | "revision": "0ce63a93a455adb3cd5e4c55f78f1232a590a5a5", 109 | "version": "4.7.2" 110 | } 111 | }, 112 | { 113 | "package": "Yaml", 114 | "repositoryURL": "https://github.com/behrang/YamlSwift.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "978901aed4025e36ebf60c040c9000c559d7c3e0", 118 | "version": "3.4.3" 119 | } 120 | }, 121 | { 122 | "package": "Yams", 123 | "repositoryURL": "https://github.com/jpsim/Yams.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "26ab35f50ea891e8edefcc9d975db2f6b67e1d68", 127 | "version": "1.0.1" 128 | } 129 | } 130 | ] 131 | }, 132 | "version": 1 133 | } 134 | -------------------------------------------------------------------------------- /Generator/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "FactoryGenerator", 8 | dependencies: [ 9 | .package(url: "https://github.com/behrang/YamlSwift.git", .upToNextMinor(from: "3.4.3")), 10 | .package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.21.1")), 11 | .package(url: "https://github.com/Kuniwak/MirrorDiffKit.git", from: "3.1.0"), 12 | .package(url: "https://github.com/kylef/Commander.git", .upToNextMinor(from: "0.8.0")), 13 | .package(url: "https://github.com/kylef/PathKit.git", .upToNextMinor(from: "0.9.1")), 14 | .package(url: "https://github.com/SwiftGen/StencilSwiftKit.git", from: "2.6.0") 15 | ], 16 | targets: [ 17 | .target( 18 | name: "FactoryGenerator", 19 | dependencies: [ 20 | "Commander", 21 | "Core", 22 | "Generator", 23 | "Parser", 24 | "PathKit" 25 | ] 26 | ), 27 | .target( 28 | name: "Core", 29 | dependencies: [ 30 | "PathKit", 31 | "Yaml" 32 | ] 33 | ), 34 | .target( 35 | name: "Parser", 36 | dependencies: [ 37 | "Core", 38 | "SourceKittenFramework" 39 | ] 40 | ), 41 | .target( 42 | name: "Generator", 43 | dependencies: [ 44 | "Core", 45 | "PathKit", 46 | "StencilSwiftKit" 47 | ] 48 | ), 49 | .testTarget( 50 | name: "ParserTests", 51 | dependencies: [ 52 | "Parser", 53 | "MirrorDiffKit" 54 | ], 55 | path: "Tests/ParserTests" 56 | ), 57 | .testTarget( 58 | name: "GeneratorTests", 59 | dependencies: [ 60 | "Generator", 61 | "MirrorDiffKit" 62 | ], 63 | path: "Tests/GeneratorTests" 64 | ) 65 | ] 66 | ) 67 | -------------------------------------------------------------------------------- /Generator/Sources/Core/Element.swift: -------------------------------------------------------------------------------- 1 | public protocol Element {} 2 | -------------------------------------------------------------------------------- /Generator/Sources/Core/Enum.swift: -------------------------------------------------------------------------------- 1 | public struct Enum: Type { 2 | public let name: String 3 | public let generics: [Generic] 4 | public let conforms: [TypeName] 5 | public let cases: [Case] 6 | 7 | public init(name: String, generics: [Generic], conforms: [TypeName], cases: [Case]) { 8 | self.name = name 9 | self.generics = generics 10 | self.conforms = conforms 11 | self.cases = cases 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Generator/Sources/Core/EnumCase.swift: -------------------------------------------------------------------------------- 1 | extension Enum { 2 | public struct Case: Element { 3 | public let name: String 4 | public let variables: [Variable] 5 | 6 | public init(name: String, variables: [Variable]) { 7 | self.name = name 8 | self.variables = variables 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Generator/Sources/Core/Generic.swift: -------------------------------------------------------------------------------- 1 | public struct Generic: Element { 2 | public let name: String 3 | public let conforms: [TypeName] 4 | 5 | public init(name: String, conforms: [TypeName]) { 6 | self.name = name 7 | self.conforms = conforms 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Generator/Sources/Core/Options.swift: -------------------------------------------------------------------------------- 1 | import PathKit 2 | import Yaml 3 | 4 | public struct Options { 5 | private let includes: [String] 6 | private let excludes: [String] 7 | 8 | public private(set) var inputs: [String] = [] 9 | public let testables: [String] 10 | public let output: String 11 | 12 | public init?(raw: String) { 13 | guard let config = try? Yaml.load(raw) else { 14 | return nil 15 | } 16 | 17 | self.includes = config["includes"].array?.compactMap({ $0.string }) ?? [config["includes"].string].compactMap({ $0 }) 18 | self.excludes = config["excludes"].array?.compactMap({ $0.string }) ?? [config["excludes"].string].compactMap({ $0 }) 19 | self.testables = config["testables"].array?.compactMap({ $0.string }) ?? [config["testables"].string].compactMap({ $0 }) 20 | self.output = config["output"].string ?? "./Factories.generated.swift" 21 | 22 | self.inputs = Array(Set(includes.map(children).flatMap({ $0 })).subtracting(Set(excludes.map(children).flatMap({ $0 })))) 23 | } 24 | 25 | private func children(with path: String) -> [String] { 26 | let content = Path(path) 27 | 28 | guard content.isDirectory else { 29 | return [content.string] 30 | } 31 | 32 | guard let children = try? Path(path).recursiveChildren() else { 33 | return [] 34 | } 35 | 36 | return children.map({ $0.string }).filter({ $0.hasSuffix(".swift") }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Generator/Sources/Core/Protocol.swift: -------------------------------------------------------------------------------- 1 | public struct Protocol: Type { 2 | public let name: String 3 | public let generics: [Generic] 4 | public let conforms: [TypeName] 5 | 6 | public init(name: String, generics: [Generic], conforms: [TypeName]) { 7 | self.name = name 8 | self.generics = generics 9 | self.conforms = conforms 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Generator/Sources/Core/Struct.swift: -------------------------------------------------------------------------------- 1 | public struct Struct: Type { 2 | public let name: String 3 | public let generics: [Generic] 4 | public let conforms: [TypeName] 5 | public let variables: [Variable] 6 | 7 | public init(name: String, generics: [Generic], conforms: [TypeName], variables: [Variable]) { 8 | self.name = name 9 | self.generics = generics 10 | self.conforms = conforms 11 | self.variables = variables 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Generator/Sources/Core/Type.swift: -------------------------------------------------------------------------------- 1 | public protocol Type { 2 | var name: String { get } 3 | var generics: [Generic] { get } 4 | var conforms: [TypeName] { get } 5 | } 6 | -------------------------------------------------------------------------------- /Generator/Sources/Core/TypeName.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct TypeName: Element, Equatable { 4 | public let name: String 5 | 6 | public init(name: String) { 7 | self.name = name.split(separator: "&").first?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Generator/Sources/Core/Variable.swift: -------------------------------------------------------------------------------- 1 | public struct Variable: Element { 2 | public let name: String 3 | public let typeName: TypeName 4 | 5 | public init(name: String, typeName: TypeName) { 6 | self.name = name 7 | self.typeName = typeName 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Generator/Sources/Core/Version.swift: -------------------------------------------------------------------------------- 1 | public struct Version { 2 | public static let current = "0.5.2" 3 | } 4 | -------------------------------------------------------------------------------- /Generator/Sources/FactoryGenerator/main.swift: -------------------------------------------------------------------------------- 1 | import Commander 2 | import Core 3 | import Generator 4 | import Parser 5 | import PathKit 6 | 7 | let main = command( 8 | Option("config", default: ".factory.yml", description: "The path of config") 9 | ) { config in 10 | guard let options = (try? Path(config).read()).flatMap(Options.init) else { 11 | print("Load Error is occured 😱 \(config)") 12 | return 13 | } 14 | 15 | do { 16 | let types = try Parser(paths: options.inputs).run() 17 | try Generator(types: types).run(with: options) 18 | } catch let error { 19 | switch error { 20 | case _ as ParseError: 21 | print("Parse Error is occured 😱") 22 | case _ as GenerateError: 23 | print("Generate Error is occured 😱") 24 | default: 25 | print("Unknown Error is occured 😱") 26 | } 27 | return 28 | } 29 | 30 | print("\(options.output) is generated 🎉") 31 | } 32 | 33 | main.run(Version.current) 34 | -------------------------------------------------------------------------------- /Generator/Sources/Generator/Arguments.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | 3 | struct Arguments { 4 | let types: Types 5 | let testables: [String] 6 | 7 | var dictionary: [String: Any] { 8 | return [ 9 | "types": types, 10 | "testables": testables 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Generator/Sources/Generator/CodeGenerator.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import StencilSwiftKit 3 | 4 | struct CodeGenerator { 5 | static func run(types: [Type], by template: Template, with options: Options) throws -> String { 6 | let arguments = Arguments(types: Types(types: types), testables: options.testables) 7 | return try StencilSwiftTemplate(templateString: template.rawValue).render(arguments.dictionary) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Generator/Sources/Generator/FileGenerator.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import PathKit 3 | 4 | struct FileGenerator { 5 | static func run(render: String, with options: Options) throws { 6 | try Path(options.output).write(render) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Generator/Sources/Generator/GenerateError.swift: -------------------------------------------------------------------------------- 1 | public enum GenerateError: Swift.Error { 2 | case render 3 | } 4 | -------------------------------------------------------------------------------- /Generator/Sources/Generator/Generator.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import PathKit 3 | import StencilSwiftKit 4 | 5 | public struct Generator { 6 | private let types: [Type] 7 | 8 | public init(types: [Type]) { 9 | self.types = types 10 | } 11 | 12 | public func run(with options: Options) throws { 13 | do { 14 | let render = try Template.allCases 15 | .map { 16 | try CodeGenerator.run(types: types, by: $0, with: options) 17 | } 18 | .reduce("") { "\($0)\($1)" } 19 | try FileGenerator.run(render: render, with: options) 20 | } catch { 21 | throw GenerateError.render 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Generator/Sources/Generator/Template.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | 3 | enum Template { 4 | case header 5 | case factory 6 | case lens 7 | 8 | var rawValue: String { 9 | switch self { 10 | case .header: 11 | return """ 12 | // Generated using FactoryProvider \(Version.current) — https://github.com/Nonchalant/FactoryProvider 13 | // DO NOT EDIT 14 | 15 | import FactoryProvider 16 | {% for testable in testables %} 17 | @testable import {{ testable }} 18 | {% endfor %} 19 | 20 | 21 | """ 22 | case .factory: 23 | return """ 24 | // MARK: - Enum 25 | 26 | {% for enum in types.enums where not enum.cases.count == 0 and enum.generics.count == 0 %} 27 | extension Factory where Type == {{ enum.name }} { 28 | static func provide() -> Type { 29 | return .{{ enum.cases.first.name }}{% if not enum.cases.first.variables.count == 0 %}( 30 | {% for variable in enum.cases.first.variables %} 31 | {% if not variable.name == "" %}{{ variable.name }}: {% endif %}Factory<{{ variable.typeName.name }}>.provide(){% if not forloop.last %},{% endif %} 32 | {% endfor %} 33 | ){% endif %} 34 | } 35 | } 36 | 37 | {% endfor %} 38 | // MARK: - Struct 39 | 40 | {% for struct in types.structs where struct.generics.count == 0 %} 41 | extension Factory where Type == {{ struct.name }} { 42 | static func provide({% for variable in struct.variables %}{{ variable.name }}: {{ variable.typeName.name }} = Factory<{{ variable.typeName.name }}>.provide(){% if not forloop.last %}, {% endif %}{% endfor %}) -> Type { 43 | return {{ struct.name }}( 44 | {% for variable in struct.variables %} 45 | {{ variable.name }}: {{ variable.name }}{% if not forloop.last %},{% endif %} 46 | {% endfor %} 47 | ) 48 | } 49 | } 50 | 51 | {% endfor %} 52 | """ 53 | case .lens: 54 | return """ 55 | // MARK: - Lens 56 | 57 | {% for struct in types.structs where struct.generics.count == 0 %} 58 | extension Lens where Type == {{ struct.name }} { 59 | {% for variable in struct.variables %} 60 | static func {{ variable.name }}() -> LensOver<{{ struct.name }}, {{ variable.typeName.name }}> { 61 | return LensOver<{{ struct.name }}, {{ variable.typeName.name }}>( 62 | getter: { $0.{{ variable.name }} }, 63 | setter: { {{ variable.name }}, base in 64 | {{ 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 %}) 65 | } 66 | ) 67 | }{% endfor %} 68 | } 69 | 70 | {% endfor %} 71 | """ 72 | } 73 | } 74 | 75 | static var allCases: [Template] { 76 | return [.header, .factory, .lens] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Generator/Sources/Generator/Types.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | 3 | struct Types { 4 | let enums: [Enum] 5 | let structs: [Struct] 6 | 7 | init(types: [Type]) { 8 | self.enums = types.compactMap { $0 as? Enum } 9 | self.structs = types.compactMap { $0 as? Struct } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/ChildParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import SourceKittenFramework 3 | 4 | struct ChildParser: ElementParser { 5 | static func parse(structure: [String : SourceKitRepresentable], raw: File) -> TypeName? { 6 | guard let kind = (structure[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init), 7 | [.class, .enum, .struct].contains(kind) else { 8 | return nil 9 | } 10 | 11 | return (structure[SwiftDocKey.name.rawValue] as? String).map(TypeName.init) 12 | } 13 | 14 | static func parse(structure: [String: SourceKitRepresentable], key: SwiftDocKey, raw: File) -> [TypeName] { 15 | guard let name = structure[SwiftDocKey.name.rawValue] as? String, 16 | let substructures = structure[key.rawValue] as? [SourceKitRepresentable] else { 17 | return [] 18 | } 19 | 20 | let innerTypes = substructures 21 | .compactMap { $0 as? [String: SourceKitRepresentable] } 22 | .map { parse(structure: $0, raw: raw) } 23 | .compactMap { $0 } 24 | 25 | guard let structures = (try? Structure(file: raw))?.dictionary[SwiftDocKey.substructure.rawValue] as? [SourceKitRepresentable] else { 26 | return innerTypes 27 | } 28 | 29 | let extensionInnerTypes = structures 30 | .compactMap { $0 as? [String: SourceKitRepresentable] } 31 | .filter { ($0[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init) == .extension } 32 | .filter { ($0[SwiftDocKey.name.rawValue] as? String) == name } 33 | .map { $0[key.rawValue] as? [SourceKitRepresentable] } 34 | .compactMap { 35 | $0? 36 | .compactMap { $0 as? [String: SourceKitRepresentable] } 37 | .map { parse(structure: $0, raw: raw) } 38 | .compactMap { $0 } 39 | } 40 | .flatMap { $0 } 41 | 42 | return innerTypes + extensionInnerTypes 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/ConformParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import SourceKittenFramework 3 | 4 | struct ConformParser: ElementParser { 5 | static func parse(structure: [String : SourceKitRepresentable], raw: File) -> TypeName? { 6 | return (structure[SwiftDocKey.name.rawValue] as? String).flatMap(TypeName.init) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/ElementParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import SourceKittenFramework 3 | 4 | protocol ElementParser { 5 | associatedtype T: Element 6 | static func parse(structure: [String: SourceKitRepresentable], key: SwiftDocKey, raw: File) -> [T] 7 | static func parse(structure: [String: SourceKitRepresentable], raw: File) -> T? 8 | } 9 | 10 | extension ElementParser { 11 | static func parse(structure: [String: SourceKitRepresentable], key: SwiftDocKey, raw: File) -> [T] { 12 | guard let substructures = structure[key.rawValue] as? [SourceKitRepresentable] else { 13 | return [] 14 | } 15 | 16 | return substructures 17 | .compactMap { $0 as? [String: SourceKitRepresentable] } 18 | .map { parse(structure: $0, raw: raw) } 19 | .compactMap { $0 } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/EnumCaseParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import Foundation 3 | import SourceKittenFramework 4 | 5 | struct EnumCaseParser: ElementParser { 6 | static func parse(structure: [String: SourceKitRepresentable], raw: File) -> Enum.Case? { 7 | guard (structure[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init) == .enumcase else { 8 | return nil 9 | } 10 | 11 | guard let substructure = (structure[SwiftDocKey.substructure.rawValue] as? [SourceKitRepresentable])?.first as? [String: SourceKitRepresentable] else { 12 | return nil 13 | } 14 | 15 | guard let name = substructure[SwiftDocKey.name.rawValue] as? String, 16 | (substructure[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init) == .enumelement else { 17 | return nil 18 | } 19 | 20 | guard let offset = structure[SwiftDocKey.offset.rawValue] as? Int64, 21 | let length = structure[SwiftDocKey.length.rawValue] as? Int64 else { 22 | return nil 23 | } 24 | 25 | let contents = raw.contents 26 | let enumCase = String(contents[contents.index(contents.startIndex, offsetBy: Int(offset)).. Variable? { 46 | let raws = raw.split(separator: ":") 47 | switch raws.count { 48 | case 2: 49 | guard let name = raws.first.map(String.init)?.trimmingCharacters(in: .whitespacesAndNewlines) else { 50 | return nil 51 | } 52 | 53 | guard let typeName = raws.dropFirst().first.map(String.init)?.trimmingCharacters(in: .whitespacesAndNewlines) else { 54 | return nil 55 | } 56 | 57 | return Variable(name: name, typeName: TypeName(name: typeName)) 58 | case 1: 59 | guard let typeName = raws.first.map(String.init)?.trimmingCharacters(in: .whitespacesAndNewlines) else { 60 | return nil 61 | } 62 | 63 | return Variable(name: "", typeName: TypeName(name: typeName)) 64 | default: 65 | return nil 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/EnumParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import SourceKittenFramework 3 | 4 | struct EnumParser: TypeParser { 5 | static func parse(structure: [String: SourceKitRepresentable], raw: File, name: String, generics: [Generic], conforms: [TypeName]) -> Enum? { 6 | return Enum( 7 | name: name, 8 | generics: generics, 9 | conforms: conforms, 10 | cases: EnumCaseParser.parse(structure: structure, key: .substructure, raw: raw) 11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/GenericParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import Foundation 3 | import SourceKittenFramework 4 | 5 | struct GenericParser { 6 | static func parse(structure: [String : SourceKitRepresentable], raw: File) -> [Generic] { 7 | guard let nameOffset = structure[SwiftDocKey.nameOffset.rawValue] as? Int64, 8 | let bodyOffset = structure[SwiftDocKey.bodyOffset.rawValue] as? Int64 else { 9 | return [] 10 | } 11 | 12 | let contents = raw.contents 13 | let name = String(contents[contents.index(contents.startIndex, offsetBy: Int(nameOffset)).."), 16 | let match = regex.firstMatch(in: name, range: NSRange(location: 0, length: name.count)), 17 | let range = Range(match.range, in: name) else { 18 | return [] 19 | } 20 | 21 | let generics = String(name[range]) 22 | 23 | return String(generics[generics.index(after: generics.startIndex).. Generic? { 30 | let raws = raw.split(separator: ":") 31 | guard let name = raws.first.map(String.init)?.trimmingCharacters(in: .whitespacesAndNewlines) else { 32 | return nil 33 | } 34 | 35 | let conforms = raws.dropFirst().first? 36 | .split(separator: "&") 37 | .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } 38 | .map(TypeName.init) ?? [] 39 | 40 | return Generic(name: name, conforms: conforms) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/ParseError.swift: -------------------------------------------------------------------------------- 1 | public enum ParseError: Swift.Error { 2 | case fileOpen 3 | case parse 4 | } 5 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/Parser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import SourceKittenFramework 3 | 4 | public struct Parser { 5 | private let paths: [String] 6 | 7 | public init(paths: [String]) { 8 | self.paths = paths 9 | } 10 | 11 | public func run() throws -> [Type] { 12 | return try paths 13 | .map { path -> File in 14 | guard let file = File(path: path) else { 15 | throw ParseError.fileOpen 16 | } 17 | return file 18 | } 19 | .map { try TypesParser(file: $0).run() } 20 | .reduce([]) { $0 + $1 } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/ProtocolParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import SourceKittenFramework 3 | 4 | struct ProtocolParser: TypeParser { 5 | static func parse(structure: [String: SourceKitRepresentable], raw: File, name: String, generics: [Generic], conforms: [TypeName]) -> Protocol? { 6 | return Protocol(name: name, generics: generics, conforms: conforms) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/StructParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import SourceKittenFramework 3 | 4 | struct StructParser: TypeParser { 5 | static func parse(structure: [String: SourceKitRepresentable], raw: File, name: String, generics: [Generic], conforms: [TypeName]) -> Struct? { 6 | let children = ChildParser.parse(structure: structure, key: .substructure, raw: raw) 7 | let variables = VariableParser.parse(structure: structure, key: .substructure, raw: raw) 8 | .map { 9 | children.contains($0.typeName) ? $0.setParent(with: name) : $0 10 | } 11 | 12 | return Struct( 13 | name: name, 14 | generics: generics, 15 | conforms: conforms, 16 | variables: variables 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/TypeParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import SourceKittenFramework 3 | 4 | protocol TypeParser { 5 | associatedtype T: Type 6 | static func parse(structure: [String: SourceKitRepresentable], raw: File, name: String, generics: [Generic], conforms: [TypeName]) -> T? 7 | static func parse(structure: [String: SourceKitRepresentable], raw: File, parentName: String?) -> T? 8 | } 9 | 10 | extension TypeParser { 11 | static func parse(structure: [String: SourceKitRepresentable], raw: File, parentName: String?) -> T? { 12 | guard let name = structure[SwiftDocKey.name.rawValue] as? String else { 13 | return nil 14 | } 15 | 16 | return parse(structure: structure, 17 | raw: raw, 18 | name: parentName.map({ "\($0).\(name)" }) ?? name, 19 | generics: GenericParser.parse(structure: structure, raw: raw), 20 | conforms: ConformParser.parse(structure: structure, key: .inheritedtypes, raw: raw)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/TypesParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import SourceKittenFramework 3 | 4 | struct TypesParser { 5 | private let file: File 6 | 7 | init(file: File) { 8 | self.file = file 9 | } 10 | 11 | func run() throws -> [Type] { 12 | do { 13 | let structure = try Structure(file: file).dictionary 14 | return parse(structure: structure) 15 | } catch { 16 | throw ParseError.parse 17 | } 18 | } 19 | 20 | private func parse(structure: [String: SourceKitRepresentable], parentName: String? = nil) -> [Type] { 21 | let types = [parse(structure: structure, parentName: parentName)].compactMap({ $0 }) 22 | 23 | guard let substructure = structure[SwiftDocKey.substructure.rawValue] as? [SourceKitRepresentable] else { 24 | return types 25 | } 26 | 27 | return substructure 28 | .compactMap { $0 as? [String: SourceKitRepresentable] } 29 | .map { parse(structure: $0, parentName: parseParentName(structure: structure, parentName: parentName)) } 30 | .reduce(types) { $0 + $1 } 31 | } 32 | 33 | private func parse(structure: [String: SourceKitRepresentable], parentName: String?) -> Type? { 34 | guard let kind = (structure[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init) else { 35 | return nil 36 | } 37 | 38 | switch kind { 39 | case .enum: 40 | return EnumParser.parse(structure: structure, raw: file, parentName: parentName) 41 | case .protocol: 42 | return ProtocolParser.parse(structure: structure, raw: file, parentName: parentName) 43 | case .struct: 44 | return StructParser.parse(structure: structure, raw: file, parentName: parentName) 45 | default: 46 | return nil 47 | } 48 | } 49 | 50 | private func parseParentName(structure: [String: SourceKitRepresentable], parentName: String?) -> String? { 51 | guard let name = structure[SwiftDocKey.name.rawValue] as? String, structure[SwiftDocKey.kind.rawValue] != nil else { 52 | return nil 53 | } 54 | 55 | return parentName.map({ "\($0).\(name)" }) ?? name 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/Variable+TypeName.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | 3 | extension Variable { 4 | func setParent(with parentName: String) -> Variable { 5 | return Variable(name: name, typeName: TypeName(name: "\(parentName).\(typeName.name)")) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Generator/Sources/Parser/VariableParser.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import Foundation 3 | import SourceKittenFramework 4 | 5 | struct VariableParser: ElementParser { 6 | static func parse(structure: [String: SourceKitRepresentable], raw: File) -> Variable? { 7 | guard let kind = (structure[SwiftDocKey.kind.rawValue] as? String).flatMap(SwiftDeclarationKind.init), kind == .varInstance else { 8 | return nil 9 | } 10 | 11 | guard let name = structure[SwiftDocKey.name.rawValue] as? String, 12 | let typeName = (structure[SwiftDocKey.typeName.rawValue] as? String).flatMap(TypeName.init) else { 13 | return nil 14 | } 15 | 16 | guard let offset = structure[SwiftDocKey.offset.rawValue] as? Int64, 17 | let length = structure[SwiftDocKey.length.rawValue] as? Int64 else { 18 | return nil 19 | } 20 | 21 | let contents = raw.contents 22 | let variable = String(contents[contents.index(contents.startIndex, offsetBy: Int(offset)).. Type { 69 | return .hang 70 | } 71 | } 72 | """ 73 | ) 74 | 75 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 76 | } 77 | 78 | func testProtocol() { 79 | let actual = try! CodeGenerator.run( 80 | types: [ 81 | Protocol( 82 | name: "Climbable", 83 | generics: [], 84 | conforms: [] 85 | ) 86 | ], 87 | by: .factory, 88 | with: options 89 | ) 90 | 91 | let expected = TemplateHelper.factory( 92 | protocols: nil 93 | ) 94 | 95 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 96 | } 97 | 98 | func testStruct() { 99 | let actual = try! CodeGenerator.run( 100 | types: [ 101 | Struct( 102 | name: "Climber", 103 | generics: [], 104 | conforms: [], 105 | variables: [ 106 | Variable(name: "name", typeName: TypeName(name: "String")), 107 | Variable(name: "age", typeName: TypeName(name: "Int")) 108 | ] 109 | ) 110 | ], 111 | by: .factory, 112 | with: options 113 | ) 114 | 115 | let expected = TemplateHelper.factory( 116 | structs: """ 117 | extension Factory where Type == Climber { 118 | static func provide(name: String = Factory.provide(), age: Int = Factory.provide()) -> Type { 119 | return Climber( 120 | name: name, 121 | age: age 122 | ) 123 | } 124 | } 125 | """ 126 | ) 127 | 128 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 129 | } 130 | 131 | // MARK: - Condition 132 | 133 | func testNested() { 134 | let actual = try! CodeGenerator.run( 135 | types: [ 136 | Struct( 137 | name: "Hold", 138 | generics: [], 139 | conforms: [], 140 | variables: [ 141 | Variable(name: "name", typeName: TypeName(name: "String")), 142 | Variable(name: "type", typeName: TypeName(name: "Type")) 143 | ] 144 | ), 145 | Enum( 146 | name: "Hold.Type", 147 | generics: [], 148 | conforms: [], 149 | cases: [ 150 | Enum.Case(name: "bucket", variables: []), 151 | Enum.Case(name: "pocket", variables: []), 152 | Enum.Case(name: "sloper", variables: []), 153 | Enum.Case(name: "tip", variables: []), 154 | Enum.Case(name: "under", variables: []) 155 | ] 156 | ) 157 | ], 158 | by: .factory, 159 | with: options 160 | ) 161 | 162 | let expected = TemplateHelper.factory( 163 | enums: """ 164 | extension Factory where Type == Hold.Type { 165 | static func provide() -> Type { 166 | return .bucket 167 | } 168 | } 169 | """, 170 | structs: """ 171 | extension Factory where Type == Hold { 172 | static func provide(name: String = Factory.provide(), type: Type = Factory.provide()) -> Type { 173 | return Hold( 174 | name: name, 175 | type: type 176 | ) 177 | } 178 | } 179 | """ 180 | ) 181 | 182 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 183 | } 184 | 185 | func testConforms() { 186 | let actual = try! CodeGenerator.run( 187 | types: [ 188 | Protocol( 189 | name: "Climbable", 190 | generics: [], 191 | conforms: [] 192 | ), 193 | Protocol( 194 | name: "Climbable2", 195 | generics: [], 196 | conforms: [TypeName(name: "Climbable")] 197 | ), 198 | Struct( 199 | name: "Wall", 200 | generics: [], 201 | conforms: [TypeName(name: "Climbable2")], 202 | variables: [] 203 | ), 204 | Struct( 205 | name: "Hold", 206 | generics: [], 207 | conforms: [TypeName(name: "Climbable2")], 208 | variables: [ 209 | Variable(name: "name", typeName: TypeName(name: "Climbable3")) 210 | ] 211 | ) 212 | ], 213 | by: .factory, 214 | with: options 215 | ) 216 | 217 | let expected = TemplateHelper.factory( 218 | protocols: nil, 219 | structs: """ 220 | extension Factory where Type == Wall { 221 | static func provide() -> Type { 222 | return Wall( 223 | ) 224 | } 225 | } 226 | 227 | extension Factory where Type == Hold { 228 | static func provide(name: Climbable3 = Factory.provide()) -> Type { 229 | return Hold( 230 | name: name 231 | ) 232 | } 233 | } 234 | """ 235 | ) 236 | 237 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 238 | } 239 | 240 | func testGenerics() { 241 | let actual = try! CodeGenerator.run( 242 | types: [ 243 | Struct( 244 | name: "Wall", 245 | generics: [ 246 | Generic(name: "T", conforms: []), 247 | Generic(name: "S", conforms: [TypeName(name: "Climbable")]) 248 | ], 249 | conforms: [], 250 | variables: [ 251 | Variable(name: "name", typeName: TypeName(name: "String")), 252 | Variable(name: "angle", typeName: TypeName(name: "Float")) 253 | ] 254 | ) 255 | ], 256 | by: .factory, 257 | with: options 258 | ) 259 | 260 | let expected = TemplateHelper.factory() 261 | 262 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 263 | } 264 | 265 | // MARK: - Enum Option 266 | 267 | func testAssociatedValues() { 268 | let actual = try! CodeGenerator.run( 269 | types: [ 270 | Enum( 271 | name: "Place", 272 | generics: [], 273 | conforms: [], 274 | cases: [ 275 | Enum.Case( 276 | name: "other", 277 | variables: [ 278 | Variable(name: "", typeName: TypeName(name: "String")), 279 | Variable(name: "", typeName: TypeName(name: "Float")) 280 | ] 281 | ) 282 | ] 283 | ) 284 | ], 285 | by: .factory, 286 | with: options 287 | ) 288 | 289 | let expected = TemplateHelper.factory( 290 | enums: """ 291 | extension Factory where Type == Place { 292 | static func provide() -> Type { 293 | return .other( 294 | Factory.provide(), 295 | Factory.provide() 296 | ) 297 | } 298 | } 299 | """ 300 | ) 301 | 302 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 303 | } 304 | 305 | func testAssociatedValuesWithLabel() { 306 | let actual = try! CodeGenerator.run( 307 | types: [ 308 | Enum( 309 | name: "Place", 310 | generics: [], 311 | conforms: [], 312 | cases: [ 313 | Enum.Case( 314 | name: "other", 315 | variables: [ 316 | Variable(name: "label1", typeName: TypeName(name: "String")), 317 | Variable(name: "label2", typeName: TypeName(name: "Encodable & Decodable")), 318 | ] 319 | ), 320 | ] 321 | ) 322 | ], 323 | by: .factory, 324 | with: options 325 | ) 326 | 327 | let expected = TemplateHelper.factory( 328 | enums: """ 329 | extension Factory where Type == Place { 330 | static func provide() -> Type { 331 | return .other( 332 | label1: Factory.provide(), 333 | label2: Factory.provide() 334 | ) 335 | } 336 | } 337 | """ 338 | ) 339 | 340 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 341 | } 342 | 343 | // MARK: - Lens 344 | 345 | // MARK: - Type 346 | 347 | func testLens() { 348 | let actual = try! CodeGenerator.run( 349 | types: [ 350 | Struct( 351 | name: "Climber", 352 | generics: [], 353 | conforms: [], 354 | variables: [ 355 | Variable(name: "name", typeName: TypeName(name: "String")), 356 | Variable(name: "age", typeName: TypeName(name: "Int")) 357 | ] 358 | ) 359 | ], 360 | by: .lens, 361 | with: options 362 | ) 363 | 364 | let expected: String = TemplateHelper.lens(structs: """ 365 | extension Lens where Type == Climber { 366 | static func name() -> LensOver { 367 | return LensOver( 368 | getter: { $0.name }, 369 | setter: { name, base in 370 | Climber(name: name, age: base.age) 371 | } 372 | ) 373 | } 374 | static func age() -> LensOver { 375 | return LensOver( 376 | getter: { $0.age }, 377 | setter: { age, base in 378 | Climber(name: base.name, age: age) 379 | } 380 | ) 381 | } 382 | } 383 | """) 384 | 385 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 386 | } 387 | 388 | func testGenericsLens() { 389 | let actual = try! CodeGenerator.run( 390 | types: [ 391 | Struct( 392 | name: "Climber", 393 | generics: [ 394 | Generic(name: "T", conforms: [TypeName(name: "Equatable")]) 395 | ], 396 | conforms: [], 397 | variables: [ 398 | Variable(name: "name", typeName: TypeName(name: "String")), 399 | Variable(name: "age", typeName: TypeName(name: "T")) 400 | ] 401 | ) 402 | ], 403 | by: .lens, 404 | with: options 405 | ) 406 | 407 | let expected: String = TemplateHelper.lens() 408 | 409 | XCTAssertEqual(actual, expected, diff(between: actual, and: expected)) 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /Generator/Tests/GeneratorTests/TemplateHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct TemplateHelper { 4 | static func factory(enums: String? = nil, protocols: String? = nil, structs: String? = nil) -> String { 5 | return """ 6 | // MARK: - Enum 7 | \(component(with: enums)) 8 | // MARK: - Struct 9 | \(component(with: structs)) 10 | 11 | """ 12 | } 13 | 14 | static func lens(structs: String? = nil) -> String { 15 | return """ 16 | // MARK: - Lens 17 | \(component(with: structs)) 18 | 19 | """ 20 | } 21 | 22 | private static func component(with types: String?, prefix: String = "\n", suffix: String = "\n") -> String { 23 | guard let types = types else { 24 | return "" 25 | } 26 | return "\(prefix)\(types)\(suffix)" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Generator/Tests/ParserTests/TypeParserTests.swift: -------------------------------------------------------------------------------- 1 | import MirrorDiffKit 2 | import XCTest 3 | @testable import Core 4 | @testable import Parser 5 | @testable import SourceKittenFramework 6 | 7 | class TypeParserTests: XCTestCase { 8 | 9 | // MARK: - Type 10 | 11 | func testEnum() { 12 | let file = File(contents: """ 13 | enum Wall { 14 | case hang 15 | case vertical 16 | case slab 17 | } 18 | """) 19 | let actual = try! TypesParser(file: file).run() 20 | 21 | let expected: [Type] = [ 22 | Enum( 23 | name: "Wall", 24 | generics: [], 25 | conforms: [], 26 | cases: [ 27 | Enum.Case(name: "hang", variables: []), 28 | Enum.Case(name: "vertical", variables: []), 29 | Enum.Case(name: "slab", variables: []) 30 | ] 31 | ) 32 | ] 33 | 34 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 35 | } 36 | 37 | func testProtocol() { 38 | let file = File(contents: """ 39 | protocol Climbable { 40 | var condition: String { get } 41 | } 42 | """) 43 | let actual = try! TypesParser(file: file).run() 44 | 45 | let expected: [Type] = [ 46 | Protocol( 47 | name: "Climbable", 48 | generics: [], 49 | conforms: [] 50 | ) 51 | ] 52 | 53 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 54 | } 55 | 56 | func testStruct() { 57 | let file = File(contents: """ 58 | struct Climber { 59 | let name: String 60 | let age: Int 61 | } 62 | """) 63 | let actual = try! TypesParser(file: file).run() 64 | 65 | let expected: [Type] = [ 66 | Struct( 67 | name: "Climber", 68 | generics: [], 69 | conforms: [], 70 | variables: [ 71 | Variable(name: "name", typeName: TypeName(name: "String")), 72 | Variable(name: "age", typeName: TypeName(name: "Int")) 73 | ] 74 | ) 75 | ] 76 | 77 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 78 | } 79 | 80 | // MARK: - Condition 81 | 82 | func testComputedProperty() { 83 | let file = File(contents: """ 84 | struct Wall { 85 | let name: String 86 | let angle: Float 87 | 88 | var isSlab: Bool { 89 | return angle < 90.0 90 | } 91 | } 92 | """) 93 | let actual = try! TypesParser(file: file).run() 94 | 95 | let expected: [Type] = [ 96 | Struct( 97 | name: "Wall", 98 | generics: [], 99 | conforms: [], 100 | variables: [ 101 | Variable(name: "name", typeName: TypeName(name: "String")), 102 | Variable(name: "angle", typeName: TypeName(name: "Float")) 103 | ] 104 | ) 105 | ] 106 | 107 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 108 | } 109 | 110 | func testExtension() { 111 | let file = File(contents: """ 112 | struct Hold { 113 | let name: String 114 | let type: Type 115 | } 116 | 117 | extension Hold { 118 | enum Type { 119 | case bucket 120 | case pocket 121 | case sloper 122 | case tip 123 | case under 124 | } 125 | } 126 | """) 127 | let actual = try! TypesParser(file: file).run() 128 | 129 | let expected: [Type] = [ 130 | Struct( 131 | name: "Hold", 132 | generics: [], 133 | conforms: [], 134 | variables: [ 135 | Variable(name: "name", typeName: TypeName(name: "String")), 136 | Variable(name: "type", typeName: TypeName(name: "Hold.Type")) 137 | ] 138 | ), 139 | Enum( 140 | name: "Hold.Type", 141 | generics: [], 142 | conforms: [], 143 | cases: [ 144 | Enum.Case(name: "bucket", variables: []), 145 | Enum.Case(name: "pocket", variables: []), 146 | Enum.Case(name: "sloper", variables: []), 147 | Enum.Case(name: "tip", variables: []), 148 | Enum.Case(name: "under", variables: []) 149 | ] 150 | ) 151 | ] 152 | 153 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 154 | } 155 | 156 | func testNested() { 157 | let file = File(contents: """ 158 | struct Climbing { 159 | struct Hold { 160 | let name: String 161 | let type: Type 162 | 163 | enum Type { 164 | case bucket 165 | case pocket 166 | case sloper 167 | case tip 168 | case under 169 | } 170 | } 171 | } 172 | """) 173 | let actual = try! TypesParser(file: file).run() 174 | 175 | let expected: [Type] = [ 176 | Struct( 177 | name: "Climbing", 178 | generics: [], 179 | conforms: [], 180 | variables: [] 181 | ), 182 | Struct( 183 | name: "Climbing.Hold", 184 | generics: [], 185 | conforms: [], 186 | variables: [ 187 | Variable(name: "name", typeName: TypeName(name: "String")), 188 | Variable(name: "type", typeName: TypeName(name: "Climbing.Hold.Type")) 189 | ] 190 | ), 191 | Enum( 192 | name: "Climbing.Hold.Type", 193 | generics: [], 194 | conforms: [], 195 | cases: [ 196 | Enum.Case(name: "bucket", variables: []), 197 | Enum.Case(name: "pocket", variables: []), 198 | Enum.Case(name: "sloper", variables: []), 199 | Enum.Case(name: "tip", variables: []), 200 | Enum.Case(name: "under", variables: []) 201 | ] 202 | ) 203 | ] 204 | 205 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 206 | } 207 | 208 | func testGenerics() { 209 | let file = File(contents: """ 210 | struct Climber { 211 | let name: T 212 | let age: U 213 | } 214 | """) 215 | let actual = try! TypesParser(file: file).run() 216 | 217 | let expected: [Type] = [ 218 | Struct( 219 | name: "Climber", 220 | generics: [ 221 | Generic(name: "T", conforms: [TypeName(name: "Equatable")]), 222 | Generic(name: "U", conforms: [TypeName(name: "Encodable"), TypeName(name: "Decodable")]) 223 | ], 224 | conforms: [], 225 | variables: [ 226 | Variable(name: "name", typeName: TypeName(name: "T")), 227 | Variable(name: "age", typeName: TypeName(name: "U")) 228 | ] 229 | ) 230 | ] 231 | 232 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 233 | } 234 | 235 | // MARK: - Enum Option 236 | 237 | func testAssociatedValues() { 238 | let file = File(contents: """ 239 | enum Place { 240 | case indoor 241 | case outdoor 242 | case other(String) 243 | } 244 | """) 245 | let actual = try! TypesParser(file: file).run() 246 | 247 | let expected: [Type] = [ 248 | Enum( 249 | name: "Place", 250 | generics: [], 251 | conforms: [], 252 | cases: [ 253 | Enum.Case(name: "indoor", variables: []), 254 | Enum.Case(name: "outdoor", variables: []), 255 | Enum.Case(name: "other(_:)", variables: [Variable(name: "", typeName: TypeName(name: "String"))]) 256 | ] 257 | ) 258 | ] 259 | 260 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 261 | } 262 | 263 | // MARK: - Protocol Option 264 | 265 | func testConforms() { 266 | let file = File(contents: """ 267 | protocol Climbable { 268 | var condition: String { get } 269 | } 270 | 271 | struct Wall: Climbable { 272 | let name: String 273 | let angle: Float 274 | } 275 | """) 276 | let actual = try! TypesParser(file: file).run() 277 | 278 | let expected: [Type] = [ 279 | Protocol( 280 | name: "Climbable", 281 | generics: [], 282 | conforms: [] 283 | ), 284 | Struct( 285 | name: "Wall", 286 | generics: [], 287 | conforms: [TypeName(name: "Climbable")], 288 | variables: [ 289 | Variable(name: "name", typeName: TypeName(name: "String")), 290 | Variable(name: "angle", typeName: TypeName(name: "Float")) 291 | ] 292 | ) 293 | ] 294 | 295 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 296 | } 297 | 298 | // MARK: - Struct Option 299 | 300 | func testImmutableDefaultValue() { 301 | let file = File(contents: """ 302 | struct Climber { 303 | let name: String 304 | let age: Int = 26 305 | } 306 | """) 307 | let actual = try! TypesParser(file: file).run() 308 | 309 | let expected: [Type] = [ 310 | Struct( 311 | name: "Climber", 312 | generics: [], 313 | conforms: [], 314 | variables: [ 315 | Variable(name: "name", typeName: TypeName(name: "String")) 316 | ] 317 | ) 318 | ] 319 | 320 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 321 | } 322 | 323 | func testImmutableClosure() { 324 | let file = File(contents: """ 325 | struct Climber { 326 | let name: String 327 | let age: Int = { 328 | return 26 329 | }() 330 | } 331 | """) 332 | let actual = try! TypesParser(file: file).run() 333 | 334 | let expected: [Type] = [ 335 | Struct( 336 | name: "Climber", 337 | generics: [], 338 | conforms: [], 339 | variables: [ 340 | Variable(name: "name", typeName: TypeName(name: "String")) 341 | ] 342 | ) 343 | ] 344 | 345 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 346 | } 347 | 348 | func testMutableDefaultValue() { 349 | let file = File(contents: """ 350 | struct Climber { 351 | let name: String 352 | var age: Int = 26 353 | } 354 | """) 355 | let actual = try! TypesParser(file: file).run() 356 | 357 | let expected: [Type] = [ 358 | Struct( 359 | name: "Climber", 360 | generics: [], 361 | conforms: [], 362 | variables: [ 363 | Variable(name: "name", typeName: TypeName(name: "String")), 364 | Variable(name: "age", typeName: TypeName(name: "Int")) 365 | ] 366 | ) 367 | ] 368 | 369 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 370 | } 371 | 372 | func testMutableClosure() { 373 | let file = File(contents: """ 374 | struct Climber { 375 | let name: String 376 | var age: Int = { 377 | return 26 378 | }() 379 | } 380 | """) 381 | let actual = try! TypesParser(file: file).run() 382 | 383 | let expected: [Type] = [ 384 | Struct( 385 | name: "Climber", 386 | generics: [], 387 | conforms: [], 388 | variables: [ 389 | Variable(name: "name", typeName: TypeName(name: "String")), 390 | Variable(name: "age", typeName: TypeName(name: "Int")) 391 | ] 392 | ) 393 | ] 394 | 395 | XCTAssert(actual =~ expected, diff(between: actual, and: expected)) 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Takeshi Ihara 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : lint release 2 | 3 | lint: 4 | pod lib lint --no-clean --allow-warnings 5 | 6 | release: 7 | pod trunk push FactoryProvider.podspec 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :factory: FactoryProvider :factory: 2 | 3 | [![Build Status](https://travis-ci.com/Nonchalant/FactoryProvider.svg?branch=master)](https://travis-ci.com/Nonchalant/FactoryProvider) 4 | [![Version](http://img.shields.io/cocoapods/v/FactoryProvider.svg?style=flat)](http://cocoadocs.org/pods/FactoryProvider) 5 | [![Platform](http://img.shields.io/cocoapods/p/FactoryProvider.svg?style=flat)](http://cocoadocs.org/pods/FactoryProvider) 6 | [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/Nonchalant/FactoryProvider/master/LICENSE.md) 7 | [![GitHub release](https://img.shields.io/github/release/Nonchalant/FactoryProvider.svg)](https://github.com/Nonchalant/FactoryProvider/releases) 8 | ![Xcode](https://img.shields.io/badge/Xcode-10.svg) 9 | ![Swift](https://img.shields.io/badge/Swift-4.2.svg) 10 | [![Swift Package Manager](https://img.shields.io/badge/Swift%20Package%20Manager-4.0.0-brightgreen.svg)](https://github.com/apple/swift-package-manager) 11 | 12 | Generate boilerplate of factory Swift framework. 13 | 14 | 15 | ## Requirements 16 | 17 | - **Swift 4+** 18 | - **Xcode 9+** 19 | 20 | 21 | ## Platforms 22 | 23 | FactoryProvider works on the following platforms: 24 | 25 | - **iOS 8+** 26 | - **Mac OSX 10.9+** 27 | - **watchOS 2+** 28 | - **tvOS 9+** 29 | 30 | 31 | ## Supports 32 | 33 | - **Enum** 34 | - **Struct** 35 | 36 | 37 | ## FactoryProvider 38 | 39 | ### 1. Installation 40 | 41 | #### CocoaPods 42 | 43 | FactoryProvider runtime is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your test target in your Podfile: 44 | 45 | ```Ruby 46 | pod "FactoryProvider" 47 | ``` 48 | 49 | And add the following `Run script` build phase to your test target's `Build Phases`: 50 | 51 | ```Bash 52 | "${PODS_ROOT}/FactoryProvider/generate" --config .factory.yml 53 | ``` 54 | 55 | After running once, locate `Factories.generated.swift` and drag it into your Xcode test target group. 56 | 57 | #### .factory.yml 58 | 59 | ```yml 60 | includes: # paths of file or directory to generate 61 | - Input/SubInput1 62 | - Input/SubInput2/Source.swift 63 | excludes: # paths of file or directory not to generate 64 | - Input/SubInput1/SubSubInput 65 | - Input/SubInput2/Source.swift 66 | testables: # testable targets 67 | - target1 68 | - target2 69 | output: output/Factories.generated.swift # path of generated file 70 | ``` 71 | 72 | ### 2. Usage 73 | 74 | You can get a instance to call `Factory.provide()`. Each properties are set to default value. 75 | 76 | ```swift 77 | struct Climber { 78 | let name: String 79 | let age: Int 80 | } 81 | 82 | let climber = Factory.provide() 83 | // Climber(name: "", age: 0) 84 | 85 | let optClimber = Factory.provide() 86 | // Optional(Climber(name: "", age: 0)) 87 | 88 | let arrayClimber = Factory<[Climber]>.provide() 89 | // [Climber(name: "", age: 0)] 90 | ``` 91 | 92 | ### 3. Lens 93 | 94 | `Factory.provide()` provides fixed instance. You can modify each property by Lens. 95 | 96 | #### Get 97 | 98 | ```swift 99 | let name = Factory.provide().name or Lens.name().get(Factory.provide()) 100 | // "" 101 | ``` 102 | 103 | #### Set 104 | 105 | ```swift 106 | import FactoryProvider 107 | 108 | let climber = Factory.provide() |> Lens.name() *~ "Climber" 109 | // Climber(name: "Climber", age: 0) 110 | ``` 111 | 112 | #### Modify 113 | 114 | ```swift 115 | import FactoryProvider 116 | 117 | let name = Factory.provide() |> Lens.name() *~ { "Climber" |> { $0 + $0 } }() 118 | // Climber(name: "ClimberClimber", age: 0) 119 | ``` 120 | 121 | #### Compose 122 | 123 | ```swift 124 | import FactoryProvider 125 | 126 | struct Climber { 127 | let id: Id 128 | let name: String 129 | 130 | struct Id { 131 | let value: String 132 | } 133 | } 134 | 135 | let climber1 = Factory.provide() 136 | // Climber(id: Id(value: ""), name: "") 137 | 138 | let climber2 = climber1 |> Lens.id() * Lens.value() *~ "id" 139 | // Climber(id: Id(value: "id"), name: "") 140 | ``` 141 | 142 | ## Notice 143 | 144 | ### Generics 145 | 146 | If you want to contains type using generics, you should use `0.4.1`. 147 | 148 | ```Ruby 149 | pod "FactoryProvider", '~> 0.4.1' 150 | ``` 151 | 152 | 153 | ## Libraries 154 | 155 | * [Commander](https://github.com/kylef/Commander) 156 | * [PathKit](https://github.com/kylef/PathKit) 157 | * [SourceKitten](https://github.com/jpsim/SourceKitten) 158 | * [StencilSwiftKit](https://github.com/SwiftGen/StencilSwiftKit) 159 | * [MirrorDiffKit](https://github.com/Kuniwak/MirrorDiffKit) 160 | 161 | 162 | ## License 163 | 164 | FactoryProvider is available under the [MIT License](LICENSE). 165 | -------------------------------------------------------------------------------- /Source/Factory+Primitive.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Factory { 4 | public static func provide() -> Type where Type == Array { 5 | return [] 6 | } 7 | } 8 | 9 | extension Factory where Type == Bool { 10 | public static func provide() -> Type { 11 | return false 12 | } 13 | } 14 | 15 | extension Factory where Type == Character { 16 | public static func provide() -> Type { 17 | return Character(Factory.provide()) 18 | } 19 | } 20 | 21 | extension Factory where Type == Data { 22 | public static func provide() -> Type { 23 | return Data() 24 | } 25 | } 26 | 27 | extension Factory where Type == Date { 28 | public static func provide() -> Type { 29 | return Date(timeIntervalSince1970: 0) 30 | } 31 | } 32 | 33 | extension Factory { 34 | public static func provide() -> Type where Type == Dictionary { 35 | return [:] 36 | } 37 | } 38 | 39 | extension Factory where Type == Double { 40 | public static func provide() -> Type { 41 | return 0 42 | } 43 | } 44 | 45 | extension Factory where Type == Float { 46 | public static func provide() -> Type { 47 | return 0 48 | } 49 | } 50 | 51 | extension Factory where Type == Int { 52 | public static func provide() -> Type { 53 | return 0 54 | } 55 | } 56 | 57 | extension Factory where Type == Int8 { 58 | public static func provide() -> Type { 59 | return 0 60 | } 61 | } 62 | 63 | extension Factory where Type == Int16 { 64 | public static func provide() -> Type { 65 | return 0 66 | } 67 | } 68 | 69 | extension Factory where Type == Int32 { 70 | public static func provide() -> Type { 71 | return 0 72 | } 73 | } 74 | 75 | extension Factory where Type == Int64 { 76 | public static func provide() -> Type { 77 | return 0 78 | } 79 | } 80 | 81 | extension Factory { 82 | public static func provide() -> Type where Type == Optional { 83 | return .none 84 | } 85 | } 86 | 87 | extension Factory where Type == String { 88 | public static func provide() -> Type { 89 | return "" 90 | } 91 | } 92 | 93 | extension Factory where Type == UInt { 94 | public static func provide() -> Type { 95 | return 0 96 | } 97 | } 98 | 99 | extension Factory where Type == UInt8 { 100 | public static func provide() -> Type { 101 | return 0 102 | } 103 | } 104 | 105 | extension Factory where Type == UInt16 { 106 | public static func provide() -> Type { 107 | return 0 108 | } 109 | } 110 | 111 | extension Factory where Type == UInt32 { 112 | public static func provide() -> Type { 113 | return 0 114 | } 115 | } 116 | 117 | extension Factory where Type == UInt64 { 118 | public static func provide() -> Type { 119 | return 0 120 | } 121 | } 122 | 123 | extension Factory where Type == URL { 124 | public static func provide() -> Type { 125 | return URL(string: Factory.provide())! 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Source/Factory.swift: -------------------------------------------------------------------------------- 1 | public struct Factory {} 2 | -------------------------------------------------------------------------------- /Source/Lens.swift: -------------------------------------------------------------------------------- 1 | public struct Lens {} 2 | -------------------------------------------------------------------------------- /Source/LensOver.swift: -------------------------------------------------------------------------------- 1 | infix operator *~: MultiplicationPrecedence 2 | infix operator |>: AdditionPrecedence 3 | 4 | public struct LensOver { 5 | private let getter: (Whole) -> Part 6 | private let setter: (Part, Whole) -> Whole 7 | 8 | public init(getter: @escaping (Whole) -> Part, setter: @escaping (Part, Whole) -> Whole) { 9 | self.getter = getter 10 | self.setter = setter 11 | } 12 | 13 | public func get(_ from: Whole) -> Part { 14 | return getter(from) 15 | } 16 | 17 | public func set(_ from: Part, _ to: Whole) -> Whole { 18 | return setter(from, to) 19 | } 20 | } 21 | 22 | public func * (lhs: LensOver, rhs: LensOver) -> LensOver { 23 | return LensOver( 24 | getter: { a in 25 | rhs.get(lhs.get(a)) 26 | }, 27 | setter: { (c, a) in 28 | lhs.set(rhs.set(c, lhs.get(a)), a) 29 | } 30 | ) 31 | } 32 | 33 | public func *~ (lhs: LensOver, rhs: B) -> (A) -> A { 34 | return { a in 35 | lhs.set(rhs, a) 36 | } 37 | } 38 | 39 | public func |> (x: A, f: (A) -> B) -> B { 40 | return f(x) 41 | } 42 | 43 | public func |> (f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C { 44 | return { g(f($0)) } 45 | } 46 | -------------------------------------------------------------------------------- /Source/Supporting Files/FactoryProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // FactoryProvider.h 3 | // FactoryProvider 4 | // 5 | // Created by Takeshi Ihara on 2018/06/06. 6 | // Copyright © 2018年 Nonchalant. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for FactoryProvider. 12 | FOUNDATION_EXPORT double FactoryProviderVersionNumber; 13 | 14 | //! Project version string for FactoryProvider. 15 | FOUNDATION_EXPORT const unsigned char FactoryProviderVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Source/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------