├── .github └── workflows │ └── ci.yml ├── .gitignore ├── API.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cartfile ├── Cartfile.resolved ├── Documents └── ko_KR │ ├── API.md │ ├── FOUNDATION_README.md │ ├── GENERATOR.md │ ├── README.md │ └── WHY_DI.md ├── FOUNDATION_README.md ├── GENERATOR.md ├── Generator ├── .gitignore ├── Package.swift ├── README.md ├── Sources │ ├── NeedleFramework │ │ ├── Entry │ │ │ ├── Generator.swift │ │ │ └── PluginizedGenerator.swift │ │ ├── Generating │ │ │ ├── DependencyGraphExporter.swift │ │ │ ├── DependencyProviderContentTask.swift │ │ │ ├── DependencyProviderDeclarerTask.swift │ │ │ ├── DependencyProviderSerializerTask.swift │ │ │ ├── FileContentLoaderTask.swift │ │ │ ├── Pluginized │ │ │ │ ├── PluginExtensionDynamicSerializerTask.swift │ │ │ │ ├── PluginExtensionSerializerTask.swift │ │ │ │ ├── PluginizedDependencyGraphExporter.swift │ │ │ │ ├── PluginizedDependencyProviderContentTask.swift │ │ │ │ ├── PluginizedDependencyProviderSerializerTask.swift │ │ │ │ └── PluginizedDynamicDependencyProviderSerializerTask.swift │ │ │ └── Serializers │ │ │ │ ├── DependencyPropsSerializer.swift │ │ │ │ ├── DependencyProviderClassNameSerializer.swift │ │ │ │ ├── DependencyProviderClassSerializer.swift │ │ │ │ ├── DependencyProviderInitBodySerializer.swift │ │ │ │ ├── DependencyProviderParamsSerializer.swift │ │ │ │ ├── DependencyProviderRegistrationSerializer.swift │ │ │ │ ├── DependencyProviderSerializer.swift │ │ │ │ ├── OutputSerializer.swift │ │ │ │ ├── Pluginized │ │ │ │ ├── PluginExtensionClassNameSerializer.swift │ │ │ │ ├── PluginExtensionContentSerializer.swift │ │ │ │ ├── PluginExtensionDynamicContentSerializer.swift │ │ │ │ ├── PluginExtensionRegistrationSerializer.swift │ │ │ │ └── PluginizedPropertiesSerializer.swift │ │ │ │ ├── PropertiesSerializer.swift │ │ │ │ ├── Serializer.swift │ │ │ │ └── SourceComponentsSerializer.swift │ │ ├── Models │ │ │ ├── AST.swift │ │ │ ├── Component.swift │ │ │ ├── ComponentExtensionNode.swift │ │ │ ├── Dependency.swift │ │ │ ├── DependencyGraphNode.swift │ │ │ ├── DependencyProvider.swift │ │ │ ├── Pluginized │ │ │ │ ├── PluginExtension.swift │ │ │ │ ├── PluginizedComponent.swift │ │ │ │ ├── PluginizedDependencyGraphNode.swift │ │ │ │ ├── PluginizedProcessedDependencyProvider.swift │ │ │ │ └── PluginizedProperty.swift │ │ │ └── Property.swift │ │ ├── Parsing │ │ │ ├── AbstractDependencyGraphParser.swift │ │ │ ├── BaseVisitor.swift │ │ │ ├── DependencyGraphParser.swift │ │ │ ├── FileFilters │ │ │ │ ├── BasicKeywordFilter.swift │ │ │ │ ├── ComponentExtensionFilter.swift │ │ │ │ ├── ComponentImplFilter.swift │ │ │ │ └── ComponentInitFilter.swift │ │ │ ├── Pluginized │ │ │ │ ├── PluginizedConstants.swift │ │ │ │ ├── PluginizedDependencyGraphParser.swift │ │ │ │ ├── Processors │ │ │ │ │ ├── NonCoreComponentLinker.swift │ │ │ │ │ ├── PluginExtensionCycleValidator.swift │ │ │ │ │ └── PluginExtensionLinker.swift │ │ │ │ └── Tasks │ │ │ │ │ ├── PluginizedASTDeclarationParserTask.swift │ │ │ │ │ └── PluginizedDeclarationsFilterTask.swift │ │ │ ├── Processors │ │ │ │ ├── AncestorCycleValidator.swift │ │ │ │ ├── ComponentConsolidator.swift │ │ │ │ ├── ComponentInstantiationValidator.swift │ │ │ │ ├── DependencyLinker.swift │ │ │ │ ├── DuplicateValidator.swift │ │ │ │ ├── ParentLinker.swift │ │ │ │ └── Processor.swift │ │ │ └── Tasks │ │ │ │ ├── ASTProducerTask.swift │ │ │ │ ├── ComponentExtensionsFilterTask.swift │ │ │ │ ├── ComponentExtensionsParserTask.swift │ │ │ │ ├── ComponentInitsFilterTask.swift │ │ │ │ ├── DeclarationsFilterTask.swift │ │ │ │ └── DeclarationsParserTask.swift │ │ └── Utilities │ │ │ ├── Constants.swift │ │ │ ├── DependencyGraphPrinter.swift │ │ │ ├── Extensions.swift │ │ │ ├── HashUtils.swift │ │ │ ├── SourceKittenFramework │ │ │ ├── library_wrapper.swift │ │ │ └── library_wrapper_sourcekitd.swift │ │ │ ├── SwiftSyntaxExtensions.swift │ │ │ └── TaskIds.swift │ └── needle │ │ ├── GenerateCommand.swift │ │ ├── PrintDependencyTreeCommand.swift │ │ ├── Version.swift │ │ ├── Version.swift.template │ │ ├── VersionCommand.swift │ │ └── main.swift ├── Tests │ └── NeedleFrameworkTests │ │ ├── Entry │ │ └── GeneratorTests.swift │ │ ├── Fixtures │ │ ├── ChildComponent.swift │ │ ├── ComponentSample.swift │ │ ├── HasComponentExtensions.swift │ │ ├── HeaderDoc.txt │ │ ├── InvalidInits │ │ │ ├── InvalidInits1.swift │ │ │ ├── InvalidInits2.swift │ │ │ ├── InvalidInits3.swift │ │ │ ├── InvalidInits4.swift │ │ │ └── InvalidInits5.swift │ │ ├── NamespacedComponentSample.swift │ │ ├── NamespacedRootComponentSample.swift │ │ ├── NonInheritanceComponent.swift │ │ ├── NonNeedleComponent.swift │ │ ├── NonSwift.json │ │ ├── OnlyDependency.swift │ │ ├── Pluginized │ │ │ ├── NamespacedComponentSample.swift │ │ │ ├── OnlyNonCoreComponent.swift │ │ │ ├── OnlyPluginExtension.swift │ │ │ └── OnlyPluginizedComponent.swift │ │ ├── PrivateSample.swift │ │ ├── RootComponentSample.swift │ │ ├── ValidInits.swift │ │ ├── nocomp.swift │ │ ├── sample.swift │ │ └── yescomp.swift │ │ ├── Generating │ │ ├── AbstractGeneratorTests.swift │ │ ├── DependencyGraphExporterTests.swift │ │ ├── DependencyProviderContentTaskTests.swift │ │ ├── DependencyProviderDeclarerTaskTests.swift │ │ ├── DependencyProviderSerializerTaskTests.swift │ │ ├── Pluginized │ │ │ ├── AbstractPluginizedGeneratorTests.swift │ │ │ ├── PluginExtensionSerializerTaskTests.swift │ │ │ ├── PluginizedDependencyGraphExporterTests.swift │ │ │ └── PluginizedDependencyProviderContentTaskTests.swift │ │ └── Serializers │ │ │ └── Pluginized │ │ │ └── PluginizedPropertiesSerializerTests.swift │ │ ├── Parsing │ │ ├── ASTProducerTaskTests.swift │ │ ├── AbstractParserTests.swift │ │ ├── AncestorCycleValidatorTests.swift │ │ ├── ComponentConsolidatorTests.swift │ │ ├── ComponentExtensionFilterTests.swift │ │ ├── ComponentInitsFilterTaskTests.swift │ │ ├── ComponentInstantiationValidatorTests.swift │ │ ├── DeclarationsFilterTaskTests.swift │ │ ├── DeclarationsParserTaskTests.swift │ │ ├── DependencyGraphParserTests.swift │ │ ├── DependencyLinkerTests.swift │ │ ├── DuplicateValidatorTests.swift │ │ ├── ParentLinkerTests.swift │ │ └── Pluginized │ │ │ ├── AbstractPluginizedParserTests.swift │ │ │ ├── NonCoreComponentLinkerTests.swift │ │ │ ├── PluginExtensionCycleValidatorTests.swift │ │ │ ├── PluginExtensionLinkerTests.swift │ │ │ ├── PluginizedDeclarationsFilterTaskTests.swift │ │ │ ├── PluginizedDeclarationsParserTaskTests.swift │ │ │ └── PluginizedDependencyGraphParserTests.swift │ │ └── Utilities │ │ └── HashUtilsTests.swift ├── bin │ ├── lib_InternalSwiftSyntaxParser.dylib │ └── needle └── xcode.xcconfig ├── Images ├── build_phases.jpeg └── logo.png ├── LICENSE.txt ├── Makefile ├── NOTICE.txt ├── NeedleFoundation.podspec ├── NeedleFoundation.xcodeproj ├── NeedleFoundationTestTests_Info.plist ├── NeedleFoundationTest_Info.plist ├── NeedleFoundationTests_Info.plist ├── NeedleFoundation_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── NeedleFoundation.xcscheme ├── Package.swift ├── README.md ├── RELEASE.md ├── Sample ├── MVC │ ├── .gitignore │ └── TicTacToe │ │ ├── Sources │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── Game │ │ │ ├── GameComponent.swift │ │ │ └── GameViewController.swift │ │ ├── Info.plist │ │ ├── LoggedIn │ │ │ ├── LoggedInComponent.swift │ │ │ ├── LoggedInViewController.swift │ │ │ └── ScoreStream.swift │ │ ├── LoggedOut │ │ │ ├── LoggedOutComponent.swift │ │ │ └── LoggedOutViewController.swift │ │ ├── NeedleGenerated.swift │ │ ├── Root │ │ │ ├── PlayersStream.swift │ │ │ ├── RootComponent.swift │ │ │ └── RootViewController.swift │ │ └── ScoreSheet │ │ │ ├── ScoreSheetComponent.swift │ │ │ └── ScoreSheetViewController.swift │ │ ├── Tests │ │ ├── Info.plist │ │ └── RootViewControllerTests.swift │ │ └── TicTacToe.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Pluginized │ ├── .gitignore │ └── TicTacToe │ │ ├── ScoreSheet │ │ ├── Info.plist │ │ ├── ScoreSheetComponent.swift │ │ ├── ScoreSheetViewController.swift │ │ └── ScoreStream.swift │ │ ├── ScoreSheetTests │ │ ├── Info.plist │ │ └── ScoreSheetTests.swift │ │ ├── TicTacToe.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ │ ├── TicTacToeCore │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── Game │ │ │ ├── GameComponent.swift │ │ │ └── GameViewController.swift │ │ ├── Info.plist │ │ ├── LoggedIn │ │ │ ├── LoggedInComponent.swift │ │ │ └── LoggedInViewController.swift │ │ ├── LoggedOut │ │ │ ├── LoggedOutComponent.swift │ │ │ └── LoggedOutViewController.swift │ │ ├── NeedleGenerated.swift │ │ ├── ObservableViewController.swift │ │ ├── PluginizedScopeLifecycle.swift │ │ ├── Root │ │ │ ├── PlayersStream.swift │ │ │ ├── RootComponent.swift │ │ │ └── RootViewController.swift │ │ └── UberPluginizedComponent.swift │ │ ├── TicTacToeCoreTests │ │ ├── Info.plist │ │ └── RootViewControllerTests.swift │ │ └── TicTacToeIntegrations │ │ ├── GameNonCoreComponent.swift │ │ ├── Info.plist │ │ └── LoggedInNonCoreComponent.swift ├── README.md ├── SwiftUI-MVVM │ ├── .gitignore │ └── TicTacToe │ │ ├── Sources │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── Game │ │ │ ├── GameComponent.swift │ │ │ ├── GameView.swift │ │ │ └── GameViewModel.swift │ │ ├── GridStack.swift │ │ ├── Info.plist │ │ ├── LoggedIn │ │ │ ├── LoggedInComponent.swift │ │ │ ├── LoggedInView.swift │ │ │ ├── LoggedInViewModel.swift │ │ │ └── ScoreStream.swift │ │ ├── LoggedOut │ │ │ ├── LoggedOutComponent.swift │ │ │ ├── LoggedOutView.swift │ │ │ └── LoggedOutViewModel.swift │ │ ├── NeedleGenerated.swift │ │ ├── ReplaySubject.swift │ │ ├── Root │ │ │ ├── PlayersStream.swift │ │ │ ├── RootComponent.swift │ │ │ ├── RootView.swift │ │ │ └── RootViewModel.swift │ │ ├── ScoreSheet │ │ │ ├── ScoreSheetComponent.swift │ │ │ ├── ScoreSheetView.swift │ │ │ └── ScoreSheetViewModel.swift │ │ └── View+Extension.swift │ │ ├── Tests │ │ ├── Info.plist │ │ └── RootViewTests.swift │ │ └── TicTacToe.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── copyright_header.txt ├── Sources ├── NeedleFoundation │ ├── Bootstrap.swift │ ├── Component.swift │ ├── Internal │ │ └── DependencyProviderRegistry.swift │ └── Pluginized │ │ ├── Internal │ │ └── PluginExtensionProviderRegistry.swift │ │ ├── NonCoreComponent.swift │ │ ├── PluginizedComponent.swift │ │ └── PluginizedScopeLifecycle.swift └── NeedleFoundationTest │ └── MockComponentPathBuilder.swift ├── Tests ├── NeedleFoundationTestTests │ └── MockComponentPathBuilderTests.swift └── NeedleFoundationTests │ ├── ComponentTests.swift │ ├── DependencyProviderRegistryTests.swift │ └── Pluginized │ └── PluginizedComponentTests.swift ├── WHY_DI.md └── foundation.xcconfig /.gitignore: -------------------------------------------------------------------------------- 1 | ## macOS 2 | *.DS_Store 3 | 4 | # Xcode Include to support Carthage for the Foundation project. 5 | # *.xcodeproj 6 | # *.xcworkspace 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | Packages/ 38 | Package.pins 39 | Package.resolved 40 | .build/ 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Needle 2 | 3 | Uber welcomes contributions of all kinds and sizes. This includes everything from from simple bug reports to large features. 4 | 5 | Before we can accept your contributions, we kindly ask you to sign our [Contributor License Agreement](https://cla-assistant.io/uber/needle). 6 | 7 | ## Workflow 8 | 9 | We love GitHub issues! 10 | 11 | For small feature requests, an issue first proposing it for discussion or demo implementation in a PR suffice. 12 | 13 | For big features, please open an issue so that we can agree on the direction, and hopefully avoid investing a lot of time on a feature that might need reworking. 14 | 15 | Small pull requests for things like typos, bug fixes, etc are always welcome. 16 | 17 | ## DOs and DON'Ts 18 | 19 | * DO follow our [coding style](https://github.com/raywenderlich/swift-style-guide) 20 | * DO include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken. 21 | * DO keep the discussions focused. When a new or related topic comes up it's often better to create new issue than to side track the discussion. 22 | 23 | * DON'T submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it. 24 | 25 | ## Guiding Principles 26 | 27 | * We allow anyone to participate in our projects. Tasks can be carried out by anyone that demonstrates the capability to complete them 28 | * Always be respectful of one another. Assume the best in others and act with empathy at all times 29 | * Collaborate closely with individuals maintaining the project or experienced users. Getting ideas out in the open and seeing a proposal before it's a pull request helps reduce redundancy and ensures we're all connected to the decision making process 30 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/needle/2a79817bc25d162eef2362f82f0fe2b005cbd3b0/Cartfile -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/needle/2a79817bc25d162eef2362f82f0fe2b005cbd3b0/Cartfile.resolved -------------------------------------------------------------------------------- /Documents/ko_KR/FOUNDATION_README.md: -------------------------------------------------------------------------------- 1 | # Needle Foundation Library 2 | 3 | ## Building and developing 4 | 5 | 먼저 필요한 패키지들을 업데이트합니다. 6 | 7 | ``` 8 | $ swift package update 9 | ``` 10 | 11 | 그 다음 command-line을 사용하여 빌드합니다. 12 | 13 | ``` 14 | $ swift build 15 | ``` 16 | 17 | 또는 Xcode 프로젝트를 만들고 IDE를 사용하여 빌드합니다. 18 | 19 | ``` 20 | $ swift package generate-xcodeproj --xcconfig-overrides foundation.xcconfig 21 | ``` 22 | 참고: 지금은 xcconfig를 사용하여 iOS deployment target settings를 전달하고 있습니다. 23 | 24 | **처음 Swift Package Manager를 사용하여 Xcode 프로젝트가 생성되면 `NeedleFoundation` 프레임워크와 `NeedleFoundationTests` 테스트 타겟 모두에 대해 Xcode 프로젝트 스킴(scheme)을 다시 만들어야 합니다.** 25 | 이는 Carthage 및 CI를 위해 필요합니다. 26 | 27 | ## 폴더 구조 28 | 29 | 다른 프로젝트가 Swift Package Manager를 통해 `NeedleFoundation`에 의존하게 하려면 foundation 프로젝트가 저장소의 루트에 있어야 합니다. 동시에 폴더 구조에 대한 SPM의 엄격한 요구 사항으로 인해 `Sources` 폴더와 같이 기본 라이브러리에 더 특정한 폴더 이름을 지정할 수 없습니다. 30 | -------------------------------------------------------------------------------- /FOUNDATION_README.md: -------------------------------------------------------------------------------- 1 | # Needle Foundation Library 2 | 3 | ## Building and developing 4 | 5 | First resolve the dependencies: 6 | 7 | ``` 8 | $ swift package update 9 | ``` 10 | 11 | You can then build from the command-line: 12 | 13 | ``` 14 | $ swift build 15 | ``` 16 | 17 | Or create an Xcode project and build using the IDE: 18 | 19 | ``` 20 | $ swift package generate-xcodeproj --xcconfig-overrides foundation.xcconfig 21 | ``` 22 | Note: For now, the xcconfig is being used to pass in the iOS deployment target settings. 23 | 24 | **Once a Xcode project is generated using Swift Package Manager, the Xcode project's schemes must be recreated for both the `NeedleFoundation` framework as well as the `NeedleFoundationTests` test target.** This is required for Carthage and CI. 25 | 26 | ## Folder Structure 27 | 28 | In order for other projects to depend on `NeedleFoundation` via Swift Package Manager, the foundation project has to be at the reposiroty's root. At the same time, the folders cannot be named more specific to the foundation library, such as the `Sources` folder, due to SPM's strict requirements on folder structure. 29 | -------------------------------------------------------------------------------- /Generator/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | *.xcodeproj 3 | *.xcworkspace 4 | *.xcscheme -------------------------------------------------------------------------------- /Generator/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.10 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Needle", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | products: [ 10 | .executable(name: "needle", targets: ["needle"]), 11 | .library(name: "NeedleFramework", targets: ["NeedleFramework"]) 12 | ], 13 | dependencies: [ 14 | .package(url: "https://github.com/apple/swift-tools-support-core", exact: "0.2.7"), 15 | .package(url: "https://github.com/uber/swift-concurrency.git", .upToNextMajor(from: "0.6.5")), 16 | .package(url: "https://github.com/uber/swift-common.git", exact: "0.5.0"), 17 | .package(url: "https://github.com/apple/swift-syntax.git", .upToNextMajor(from: "510.0.0")), 18 | ], 19 | targets: [ 20 | .target( 21 | name: "NeedleFramework", 22 | dependencies: [ 23 | .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), 24 | .product(name: "Concurrency", package: "swift-concurrency"), 25 | .product(name: "SourceParsingFramework", package: "swift-common"), 26 | .product(name: "SwiftSyntax", package: "swift-syntax"), 27 | .product(name: "SwiftParser", package: "swift-syntax"), 28 | ]), 29 | .testTarget( 30 | name: "NeedleFrameworkTests", 31 | dependencies: ["NeedleFramework"], 32 | exclude: [ 33 | "Fixtures", 34 | ]), 35 | .executableTarget( 36 | name: "needle", 37 | dependencies: [ 38 | "NeedleFramework", 39 | .product(name: "CommandFramework", package: "swift-common") 40 | ]), 41 | ], 42 | swiftLanguageVersions: [.v5] 43 | ) 44 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/FileContentLoaderTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceParsingFramework 20 | 21 | /// A task that loads the content of a file path and returns it as a 22 | /// `String`. 23 | class FileContentLoaderTask: AbstractTask { 24 | 25 | /// Initializer. 26 | /// 27 | /// - parameter filePath: The path to the file to be loaded. 28 | init(filePath: String) { 29 | self.filePath = filePath 30 | super.init(id: TaskIds.fileContentLoaderTask.rawValue) 31 | } 32 | 33 | /// Execute the task and returns the file content. 34 | /// 35 | /// - returns: The file content as `String`. 36 | /// - throws: Any error occurred during execution. 37 | override func execute() throws -> String { 38 | let url = URL(fileURLWithPath: filePath) 39 | return try CachedFileReader.instance.content(forUrl: url) 40 | } 41 | 42 | // MARK: - Private 43 | 44 | private let filePath: String 45 | } 46 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionDynamicSerializerTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | 20 | /// The task that generates the declaration and registration of the 21 | /// plugin extension provider for a specific pluginized component. 22 | class PluginExtensionDynamicSerializerTask : AbstractTask { 23 | 24 | /// Initializer. 25 | /// 26 | /// - parameter component: The pluginized component that requires the 27 | /// plugin extension provider. 28 | init(component: PluginizedComponent) { 29 | self.component = component 30 | super.init(id: TaskIds.pluginExtensionSerializerTask.rawValue) 31 | } 32 | 33 | /// Execute the task and returns the data model. 34 | /// 35 | /// - returns: The `SerializedProvider`. 36 | override func execute() -> SerializedProvider { 37 | let content = PluginExtensionDynamicContentSerializer(component: component).serialize() 38 | return SerializedProvider(content: content, registration: "", attributes: ProviderAttributes()) 39 | } 40 | 41 | // MARK: - Private 42 | 43 | private let component: PluginizedComponent 44 | } 45 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionSerializerTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | 20 | /// The task that generates the declaration and registration of the 21 | /// plugin extension provider for a specific pluginized component. 22 | class PluginExtensionSerializerTask : AbstractTask { 23 | 24 | /// Initializer. 25 | /// 26 | /// - parameter component: The pluginized component that requires the 27 | /// plugin extension provider. 28 | init(component: PluginizedComponent) { 29 | self.component = component 30 | super.init(id: TaskIds.pluginExtensionSerializerTask.rawValue) 31 | } 32 | 33 | /// Execute the task and returns the data model. 34 | /// 35 | /// - returns: The `SerializedProvider`. 36 | override func execute() -> SerializedProvider { 37 | let content = PluginExtensionContentSerializer(component: component).serialize() 38 | let registration = PluginExtensionRegistrationSerializer(component: component).serialize() 39 | return SerializedProvider(content: content, registration: registration, attributes: ProviderAttributes()) 40 | } 41 | 42 | // MARK: - Private 43 | 44 | private let component: PluginizedComponent 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDynamicDependencyProviderSerializerTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | 20 | /// The task that serializes a list of pluginized processed dependency 21 | /// providers into exportable foramt. 22 | class PluginizedDynamicDependencyProviderSerializerTask: AbstractTask<[SerializedProvider]> { 23 | 24 | /// Initializer. 25 | /// 26 | /// - parameter providers: The pluginized processed dependency provider 27 | /// to serialize. 28 | init(component: Component, providers: [PluginizedProcessedDependencyProvider]) { 29 | self.component = component 30 | self.providers = providers 31 | super.init(id: TaskIds.pluginizedDependencyProviderSerializerTask.rawValue) 32 | } 33 | 34 | /// Execute the task and returns the in-memory serialized dependency 35 | /// provider data models. 36 | /// 37 | /// - returns: The list of `SerializedProvider`. 38 | override func execute() -> [SerializedProvider] { 39 | guard !providers.isEmpty else { 40 | return [] 41 | } 42 | let serilizer = DependencyPropsSerializer(component: component) 43 | let result = SerializedProvider(content: serilizer.serialize(), registration: "", attributes: ProviderAttributes()) 44 | return [result] 45 | } 46 | 47 | // MARK: - Private 48 | 49 | private let component: Component 50 | private let providers: [PluginizedProcessedDependencyProvider] 51 | } 52 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/DependencyPropsSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | class DependencyPropsSerializer: Serializer { 20 | 21 | init(component: Component) { 22 | self.component = component 23 | } 24 | 25 | func serialize() -> String { 26 | if component.isLeaf { 27 | return """ 28 | extension \(component.name): NeedleFoundation.Registration { 29 | public func registerItems() { 30 | \(serialize(component.dependency)) 31 | } 32 | } 33 | 34 | """ 35 | } else { 36 | return """ 37 | extension \(component.name): NeedleFoundation.Registration { 38 | public func registerItems() { 39 | \(serialize(component.dependency)) 40 | \(serialize(component.properties)) 41 | } 42 | } 43 | 44 | """ 45 | } 46 | } 47 | 48 | // MARK: - Private 49 | 50 | private func serialize(_ dependency: Dependency) -> String { 51 | let dependencyName = dependency.name 52 | return dependency.properties.map { property in 53 | return " keyPathToName[\\\(dependencyName).\(property.name)] = \"\(property.name)-\(property.type)\"" 54 | }.joined(separator: "\n") 55 | } 56 | 57 | private func serialize(_ properties: [Property]) -> String { 58 | return properties.filter { property in 59 | !property.isInternal 60 | }.map { property in 61 | return " localTable[\"\(property.name)-\(property.type)\"] = { [unowned self] in self.\(property.name) as Any }" 62 | }.joined(separator: "\n") 63 | } 64 | 65 | private let component: Component 66 | } 67 | 68 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/DependencyProviderInitBodySerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A serializer that produces the initializer body code for the dependency 20 | /// provider. 21 | class DependencyProviderBaseInitSerializer: Serializer { 22 | 23 | /// Initializer. 24 | /// 25 | /// - parameter provider: The provider to generate initializer body 26 | /// source code for. 27 | init(provider: ProcessedDependencyProvider) { 28 | self.provider = provider 29 | } 30 | 31 | /// Serialize the data model and produce the initializer body code. 32 | /// 33 | /// - returns: The initializer body source code. 34 | func serialize() -> String { 35 | let arguments = provider.levelMap 36 | .sorted(by: { $0.key < $1.key }) 37 | .map { (componentType: String, level: Int) in 38 | return "\(componentType.lowercasedFirstChar()): \(componentType)" 39 | } 40 | .joined(separator: ", ") 41 | let body = provider.levelMap 42 | .sorted(by: { $0.key < $1.key }) 43 | .map { (componentType: String, level: Int) in 44 | return " self.\(componentType.lowercasedFirstChar()) = \(componentType.lowercasedFirstChar())" 45 | } 46 | .joined(separator: "\n") 47 | return """ 48 | init(\(arguments)) { 49 | \(body) 50 | } 51 | """ 52 | } 53 | 54 | // MARK: - Private 55 | 56 | private let provider: ProcessedDependencyProvider 57 | } 58 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/DependencyProviderParamsSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | /// A serializer that produces the params to pass to the dependency 18 | /// provider. 19 | final class DependencyProviderParamsSerializer: Serializer { 20 | 21 | /// Initializer. 22 | /// 23 | /// - parameter provider: The provider to generate params source code for. 24 | init(provider: ProcessedDependencyProvider) { 25 | self.provider = provider 26 | } 27 | 28 | /// Serialize the data model and produce the params code. 29 | /// 30 | /// - returns: The params source code. 31 | func serialize() -> String { 32 | return provider.levelMap 33 | .sorted(by: { $0.key < $1.key }) 34 | .map { (componentType: String, level: Int) in 35 | return "\(componentType.lowercasedFirstChar()): parent\(level)(component) as! \(componentType)" 36 | } 37 | .joined(separator: ", ") 38 | } 39 | 40 | // MARK: - Private 41 | 42 | private let provider: ProcessedDependencyProvider 43 | } 44 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/DependencyProviderRegistrationSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A serializer that produces the registration code for the dependency 20 | /// provider. 21 | class DependencyProviderRegistrationSerializer: Serializer { 22 | 23 | /// Initializer. 24 | /// 25 | /// - parameter provider: The provider to generate registration code 26 | /// - parameter factoryFuncNameSerializer: The serializer to generate the factory func name 27 | /// for. 28 | init(provider: ProcessedDependencyProvider, factoryFuncNameSerializer: Serializer) { 29 | self.provider = provider 30 | self.factoryFuncNameSerializer = factoryFuncNameSerializer 31 | } 32 | 33 | /// Serialize the data model and produce the registration source code. 34 | /// 35 | /// - returns: The registration source code. 36 | func serialize() -> String { 37 | let factoryName = provider.isEmptyDependency ? "factoryEmptyDependencyProvider" : factoryFuncNameSerializer.serialize() 38 | return """ 39 | registerProviderFactory("\(provider.unprocessed.pathString)", \(factoryName))\n 40 | """ 41 | } 42 | 43 | // MARK: - Private 44 | 45 | private let provider: ProcessedDependencyProvider 46 | private let factoryFuncNameSerializer: Serializer 47 | } 48 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/DependencyProviderSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A serializer that produces the source code for the dependency 20 | /// provider. It's mostly empty as the core logic lives in the 21 | /// class. 22 | class DependencyProviderFuncSerializer: Serializer { 23 | 24 | init(provider: ProcessedDependencyProvider, funcNameSerializer: Serializer, classNameSerializer: Serializer, paramsSerializer: Serializer) { 25 | self.provider = provider 26 | self.funcNameSerializer = funcNameSerializer 27 | self.classNameSerializer = classNameSerializer 28 | self.paramsSerializer = paramsSerializer 29 | } 30 | 31 | /// Serialize the data model and produce the entire dependency provider 32 | /// source code. 33 | /// 34 | /// - returns: The entire source code for the dependency provider. 35 | func serialize() -> String { 36 | return """ 37 | /// \(provider.unprocessed.pathString) 38 | private func \(funcNameSerializer.serialize())(_ component: NeedleFoundation.Scope) -> AnyObject { 39 | return \(classNameSerializer.serialize())(\(paramsSerializer.serialize())) 40 | }\n 41 | """ 42 | } 43 | 44 | // MARK: - Private 45 | 46 | private let provider: ProcessedDependencyProvider 47 | private let funcNameSerializer: Serializer 48 | private let classNameSerializer: Serializer 49 | private let paramsSerializer: Serializer 50 | } 51 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/Pluginized/PluginExtensionClassNameSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A serializer that produces the class name for the plugin extension proider. 20 | class PluginExtensionClassNameSerializer: Serializer { 21 | 22 | /// Initializer. 23 | /// 24 | /// - parameter component: The pluginized component to generate class name for. 25 | init(component: PluginizedComponent) { 26 | self.component = component 27 | } 28 | 29 | /// Serialize the data model and produce the class name code. 30 | /// 31 | /// - returns: The class name code. 32 | func serialize() -> String { 33 | return "\(component.pluginExtension.name)Provider" 34 | } 35 | 36 | // MARK: - Private 37 | 38 | private let component: PluginizedComponent 39 | } 40 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/Pluginized/PluginExtensionDynamicContentSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A serializer that produces the class definition source code for the 20 | /// plugin extension provider. 21 | class PluginExtensionDynamicContentSerializer: Serializer { 22 | 23 | /// Initializer. 24 | /// 25 | /// - parameter component: The pluginized component for which is associated 26 | /// with this plugin extension 27 | init(component: PluginizedComponent) { 28 | self.component = component 29 | } 30 | 31 | /// Serialize the data model and produce the source code. 32 | /// 33 | /// - returns: The plugin extension class implemention source code. 34 | func serialize() -> String { 35 | let properties = serialize(properties: component.pluginExtension.properties) 36 | 37 | return """ 38 | /// \(component.data.name) plugin extension 39 | extension \(component.data.name): NeedleFoundation.ExtensionRegistration { 40 | public func registerExtensionItems() { 41 | \(properties) 42 | } 43 | } 44 | 45 | """ 46 | } 47 | 48 | func serialize(properties: [Property]) -> String { 49 | return properties.map { property in 50 | return " extensionToName[\\\(component.pluginExtension.name).\(property.name)] = \"\(property.name)-\(property.type)\"" 51 | }.joined(separator: "\n") 52 | } 53 | 54 | // MARK: - Private 55 | 56 | private let component: PluginizedComponent 57 | } 58 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/Pluginized/PluginExtensionRegistrationSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A serializer that produces the registration code for the plugin extension 20 | /// provider. 21 | class PluginExtensionRegistrationSerializer: Serializer { 22 | 23 | /// Initializer. 24 | /// 25 | /// - parameter component: The pluginized component to generate registration 26 | /// source code for 27 | init(component: PluginizedComponent) { 28 | self.component = component 29 | } 30 | 31 | /// Serialize the data model and produce the registration source code. 32 | /// 33 | /// - returns: The registration source code. 34 | func serialize() -> String { 35 | let className = PluginExtensionClassNameSerializer(component: component).serialize() 36 | return """ 37 | __PluginExtensionProviderRegistry.instance.registerPluginExtensionProviderFactory(for: "\(component.data.name)") { component in 38 | return \(className)(component: component) 39 | }\n 40 | """ 41 | } 42 | 43 | // MARK: - Private 44 | 45 | private let component: PluginizedComponent 46 | } 47 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/PropertiesSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A serializer that produces the dependency properties code for the 20 | /// processed properties. 21 | class PropertiesSerializer: Serializer { 22 | 23 | /// Initializer. 24 | /// 25 | /// - parameter properties: The properties to generate dependency 26 | /// provider property getter code for. 27 | init(processedProperties: [ProcessedProperty]) { 28 | self.processedProperties = processedProperties 29 | } 30 | 31 | /// Serialize the property models and produce the source code. 32 | /// 33 | /// - returns: The dependency properties source code. 34 | func serialize() -> String { 35 | return processedProperties 36 | .map { (property: ProcessedProperty) in 37 | serialize(property) 38 | } 39 | .joined(separator: "\n") 40 | } 41 | 42 | // MARK: - Private 43 | 44 | private let processedProperties: [ProcessedProperty] 45 | 46 | private func serialize(_ property: ProcessedProperty) -> String { 47 | return """ 48 | var \(property.unprocessed.name): \(property.unprocessed.type) { 49 | return \(property.sourceComponentType.lowercasedFirstChar()).\(property.unprocessed.name) 50 | } 51 | """ 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/Serializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A utility unit that serializes data models into source code. 20 | protocol Serializer: AnyObject { 21 | 22 | /// Serialize the data model into source code. 23 | /// 24 | /// - returns: The source code `String`. 25 | func serialize() -> String 26 | } 27 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Generating/Serializers/SourceComponentsSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A serializer that produces the stored source component properties 20 | /// code based on the level map information. 21 | class SourceComponentsSerializer: Serializer { 22 | 23 | /// Initializer. 24 | /// 25 | /// - parameter componentTypes: The types to generate source component 26 | /// properties code for. 27 | init(componentTypes: [String]) { 28 | self.componentTypes = componentTypes 29 | } 30 | 31 | /// Serialize the data model and produce property declaration code. 32 | /// 33 | /// - returns: The registration code. 34 | func serialize() -> String { 35 | return componentTypes.map { (componentType: String) in 36 | return " private let \(componentType.lowercasedFirstChar()): \(componentType)" 37 | } 38 | .joined(separator: "\n") 39 | } 40 | 41 | // MARK: - Private 42 | 43 | private let componentTypes: [String] 44 | } 45 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Models/AST.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import SwiftSyntax 19 | 20 | /// A data model representing a abstract syntax tree of a Swift source 21 | /// file including its import statements. 22 | struct AST { 23 | /// The hash of the source file of this ast 24 | let sourceHash: String 25 | /// Syntax representation of this source file created by SwiftSyntax 26 | let sourceFileSyntax: SourceFileSyntax 27 | /// Path to the file 28 | let filePath: String 29 | } 30 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Models/ComponentExtensionNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A data model representing a list of `Component` extensions parsed 20 | /// from a single file. 21 | struct ComponentExtensionNode { 22 | /// The list of component extensions in this node. 23 | let extensions: [ASTComponentExtension] 24 | /// The list of import statements including the `import` keyword. 25 | let imports: [String] 26 | } 27 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Models/Dependency.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// The special empty dependency data model. 20 | let emptyDependency = Dependency(name: "EmptyDependency", properties: [], sourceHash: "UniqueEmptyDependencyHash", filePath: "/dev/null") 21 | 22 | /// A data model representing a dependency protocol of a NeedleFoundation 23 | /// `Component`. 24 | // This is separate from the `Component` data model, since a component's 25 | // dependency protocol may be declared in a separate file. 26 | struct Dependency: Equatable { 27 | /// The name of the dependency protocol. 28 | let name: String 29 | /// The list of dependency properties. 30 | let properties: [Property] 31 | /// The hash of the file where this dependency is declared 32 | let sourceHash: String 33 | /// Path to the file where this dependency is declared 34 | let filePath: String 35 | 36 | /// `true` if this dependency prootocol is the `EmptyDependency`. 37 | /// `false` otherwise. 38 | var isEmptyDependency: Bool { 39 | return Dependency.isEmptyDependency(name: name) 40 | } 41 | 42 | /// Check if the dependency name is an empty dependency. 43 | /// 44 | /// - returns: `true` if this dependency prootocol is the `EmptyDependency`. 45 | /// `false` otherwise. 46 | static func isEmptyDependency(name: String) -> Bool { 47 | return name == "EmptyDependency" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Models/DependencyGraphNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A data model representing a list of `Component` and/or `Dependency` parsed from 20 | /// a single file. 21 | struct DependencyGraphNode { 22 | /// The list of components in this node. 23 | let components: [ASTComponent] 24 | /// The list of dependencies in this node. 25 | let dependencies: [Dependency] 26 | /// The list of import statements including the `import` keyword. 27 | let imports: [String] 28 | } 29 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Models/Pluginized/PluginExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A data model representing a plugin extension protocol of a NeedleFoundation 20 | /// `PluginizedComponent`. 21 | // This is separate from the `PluginizedComponent` data model, since its 22 | // plugin extension protocol may be declared in a separate file. 23 | struct PluginExtension: Equatable { 24 | /// The name of the plugin extension protocol. 25 | let name: String 26 | /// The list of properties. 27 | let properties: [Property] 28 | } 29 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Models/Pluginized/PluginizedDependencyGraphNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// An extended data model representing a node in the dependency graph. 20 | /// The components may be pluginized components or regular ones. 21 | struct PluginizedDependencyGraphNode { 22 | /// The list of pluginized components in this node. 23 | let pluginizedComponents: [PluginizedASTComponent] 24 | /// The list of non-core components in this node. 25 | let nonCoreComponents: [ASTComponent] 26 | /// The list of plugin extensions in this node. 27 | let pluginExtensions: [PluginExtension] 28 | /// The list of regular components in this node. 29 | let components: [ASTComponent] 30 | /// The list of dependencies in this node. 31 | let dependencies: [Dependency] 32 | /// The list of import statements including the `import` keyword. 33 | let imports: [String] 34 | } 35 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Models/Pluginized/PluginizedProcessedDependencyProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// An extended data model representing a dependency provider to be generated 20 | /// for a specific path of a component. 21 | struct PluginizedProcessedDependencyProvider { 22 | /// The actual data of this dependency provider. 23 | /// Note that in this struct, the `processedProperties` are always empty 24 | let data: ProcessedDependencyProvider 25 | /// This is a (richer) replacement for the processed properties in the data 26 | /// struct 27 | let processedProperties: [PluginizedProcessedProperty] 28 | 29 | /// Initializer. 30 | /// 31 | /// - parameter unprocessed: The unprocessed data model. 32 | /// - parameter levelMap: The map of component type names to the number of 33 | /// levels between provider and component that needs it. to check for 34 | /// auxiliary properties. 35 | /// - parameter processedProperties: TThe properties with their source 36 | /// components filled in. 37 | init(unprocessed: DependencyProvider, levelMap: [String: Int], processedProperties: [PluginizedProcessedProperty]) { 38 | self.data = ProcessedDependencyProvider(unprocessed: unprocessed, levelMap: levelMap, processedProperties: []) 39 | self.processedProperties = processedProperties 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Models/Pluginized/PluginizedProperty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// When a property is found is the auxillary source, this lets us know the 20 | /// source 21 | enum AuxillarySourceType { 22 | /// Situation where a core component is getting a property from the 23 | /// plugin extension protocol. 24 | case pluginExtension 25 | /// Situation where a non-core component gets the property from a 26 | /// ancestor non-core component. 27 | case nonCoreComponent 28 | } 29 | 30 | /// An extended data model representing a single dependency property that 31 | /// has gone through generation processing. 32 | struct PluginizedProcessedProperty: Equatable, Hashable { 33 | /// The actual data of this dependency property. 34 | let data: ProcessedProperty 35 | /// If the property was found in the auxillary scope, this tells us the 36 | /// type of that scope. 37 | let auxillarySourceType: AuxillarySourceType? 38 | /// If the property was found in the auxillary scope, this is the type 39 | /// name of the auxillary object. 40 | let auxillarySourceName: String? 41 | } 42 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Models/Property.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A data model representing a dependency property that is either provided by a 20 | /// `Component` or required by one. 21 | struct Property: Hashable { 22 | /// The variable name. 23 | let name: String 24 | /// The property type `String`. 25 | let type: String 26 | /// If this property is internal 27 | let isInternal: Bool 28 | 29 | init(name: String, type: String, isInternal: Bool = false) { 30 | self.name = name 31 | self.type = type 32 | self.isInternal = isInternal 33 | } 34 | 35 | static func ==(lhs: Property, rhs: Property) -> Bool { 36 | return lhs.name == rhs.name && lhs.type == rhs.type 37 | } 38 | } 39 | 40 | /// A data model representing a single dependency property that has gone through 41 | /// generation processing. 42 | struct ProcessedProperty: Equatable, Hashable { 43 | /// The unprocessed property we started with. 44 | let unprocessed: Property 45 | /// Type of the Component where this property is satisfied. 46 | let sourceComponentType: String 47 | } 48 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/FileFilters/BasicKeywordFilter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SourceParsingFramework 18 | 19 | /// A filter that does a simple String contains check for Needle related protocols/classes 20 | /// in a file. 21 | class BasicKeywordFilter: FileFilter { 22 | private let content: String 23 | 24 | init(content: String) { 25 | self.content = content 26 | } 27 | 28 | func filter() -> Bool { 29 | return content.containsAny(in: needleKeywords) 30 | } 31 | } 32 | 33 | fileprivate let needleKeywords = [componentClassName, dependencyProtocolName, pluginExtensionProtocolName] 34 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/FileFilters/ComponentExtensionFilter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import SourceParsingFramework 19 | 20 | /// A filter that checks if the given content contains an extension of 21 | /// any one of the components. 22 | class ComponentExtensionFilter: FileFilter { 23 | 24 | /// Initializer. 25 | /// 26 | /// - parameter content: The content to be filtered. 27 | /// - parameter components: All the components parsed out. 28 | init(content: String, components: [ASTComponent]) { 29 | self.content = content 30 | self.componentNames = components.map { (component: ASTComponent) -> String in 31 | component.name 32 | } 33 | } 34 | 35 | /// Execute the filter. 36 | /// 37 | /// - returns: `true` if the file content contains component class 38 | /// extensions of the given parsed components. 39 | func filter() -> Bool { 40 | // Use simple string matching first since it's more performant. 41 | return content.contains("extension") 42 | } 43 | 44 | // MARK: - Private 45 | 46 | private let content: String 47 | private let componentNames: [String] 48 | } 49 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/FileFilters/ComponentImplFilter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import SourceParsingFramework 19 | 20 | /// A filter that performs checks if the file content contains any 21 | /// component class implementations. 22 | class ComponentImplFilter: KeywordRegexFilter { 23 | 24 | /// Initializer. 25 | /// 26 | /// - parameter content: The content to be filtered. 27 | init(content: String) { 28 | super.init(content: content, keyword: componentClassName, regex: Regex.foundationGenericInheritanceRegex(forClass: componentClassName)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/FileFilters/ComponentInitFilter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import SourceParsingFramework 19 | 20 | /// The regex pattern that matches any class instantiation expressions 21 | /// with the first capture group capturing the name of the class. 22 | let componentInstantiationRegex = Regex("\\s+([A-Z]\\w+)\\s*\\(\\s*parent\\s*:\\s*") 23 | 24 | /// A filter that performs checks if the file content contains any 25 | /// component instantiations. 26 | class ComponentInitFilter: KeywordRegexFilter { 27 | 28 | /// Initializer. 29 | /// 30 | /// - parameter content: The content to be filtered. 31 | init(content: String) { 32 | super.init(content: content, keyword: "parent", regex: componentInstantiationRegex) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/Pluginized/PluginizedConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | /// The name of the protocol that all PluginExtension protocols inherit from 18 | let pluginExtensionProtocolName = "PluginExtension" 19 | 20 | /// The name of the class that all PluginizedComponent classes inherit from 21 | let pluginizedComponentClassName = "PluginizedComponent" 22 | 23 | /// The name of the class that all Uber PluginizedComponent classes inherit from 24 | let uberPluginizedComponentClassName = "UberPluginizedComponent" 25 | 26 | /// The name of the class that all NonCoreComponent classes inherit from 27 | let nonCoreComponentClassName = "NonCoreComponent" 28 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/Processors/ComponentConsolidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import SourceParsingFramework 19 | 20 | /// A post processing utility class that consolidates component extensions 21 | /// with the corresponding components. 22 | class ComponentConsolidator: Processor { 23 | 24 | /// Initializer. 25 | /// 26 | /// - parameter components: The components to consolidate into. 27 | /// - parameter componentExtensions: The component extensions to 28 | /// consolidate with. 29 | init(components: [ASTComponent], componentExtensions: [ASTComponentExtension]) { 30 | self.components = components 31 | self.componentExtensions = componentExtensions 32 | } 33 | 34 | /// Process the data models. 35 | func process() throws { 36 | let nameToComponent = components.spm_createDictionary { (component: ASTComponent) -> (String, ASTComponent) in 37 | (component.name, component) 38 | } 39 | for componentExtension in componentExtensions { 40 | let component = nameToComponent[componentExtension.name] 41 | if let component = component { 42 | component.properties.append(contentsOf: componentExtension.properties) 43 | component.expressionCallTypeNames.append(contentsOf: componentExtension.expressionCallTypeNames) 44 | } else { 45 | throw GenericError.withMessage("\(componentExtension.name) only has extension but missing declaration.") 46 | } 47 | } 48 | } 49 | 50 | // MARK: - Private 51 | 52 | private let components: [ASTComponent] 53 | private let componentExtensions: [ASTComponentExtension] 54 | } 55 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/Processors/DependencyLinker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import SourceParsingFramework 19 | 20 | /// A post processing utility class that links components to their dependency 21 | /// protocols. 22 | class DependencyLinker: Processor { 23 | 24 | /// Initializer. 25 | /// 26 | /// - parameter components: The components to link. 27 | /// - parameter dependencies: The dependency protocols to link. 28 | init(components: [ASTComponent], dependencies: [Dependency]) { 29 | self.components = components 30 | self.dependencies = dependencies 31 | } 32 | 33 | /// Process the data models. 34 | func process() throws { 35 | var nameToDependency: [String: Dependency] = [emptyDependency.name: emptyDependency] 36 | for dependency in dependencies { 37 | nameToDependency[dependency.name] = dependency 38 | } 39 | for component in components { 40 | if let dependency = nameToDependency[component.dependencyProtocolName] { 41 | component.dependencyProtocol = dependency 42 | } else if !Dependency.isEmptyDependency(name: component.dependencyProtocolName) { 43 | throw GenericError.withMessage("Missing dependency protocol data model with name \(component.dependencyProtocolName), for \(component.name).") 44 | } 45 | } 46 | } 47 | 48 | // MARK: - Private 49 | 50 | private let components: [ASTComponent] 51 | private let dependencies: [Dependency] 52 | } 53 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/Processors/ParentLinker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A post processing utility class that links components to their parents. 20 | class ParentLinker: Processor { 21 | 22 | /// Initializer. 23 | /// 24 | /// - parameter components: The components to link. 25 | init(components: [ASTComponent]) { 26 | self.components = components 27 | } 28 | 29 | /// Process the data models. 30 | func process() throws { 31 | let nameToComponent = components.spm_createDictionary { (component: ASTComponent) -> (String, ASTComponent) in 32 | (component.name, component) 33 | } 34 | for component in components { 35 | for typeName in component.expressionCallTypeNames { 36 | if let childComponent = nameToComponent[typeName] { 37 | childComponent.parents.append(component) 38 | component.isLeaf = false 39 | } 40 | } 41 | } 42 | } 43 | 44 | // MARK: - Private 45 | 46 | private let components: [ASTComponent] 47 | } 48 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/Processors/Processor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A post parsing utility unit that processes the parssed data model. 20 | protocol Processor { 21 | 22 | /// Process the data models. 23 | /// 24 | /// - throws: `GenericError` if any errors occurred during processing. 25 | func process() throws 26 | } 27 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/Tasks/ASTProducerTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceParsingFramework 20 | import SwiftParser 21 | 22 | /// A task that parses a Swift source content and produces Swift AST that 23 | /// can then be parsed into the dependency graph. 24 | class ASTProducerTask: AbstractTask { 25 | 26 | /// Initializer. 27 | /// 28 | /// - parameter sourceUrl: The source URL. 29 | /// - parameter sourceContent: The source content to be parsed into AST. 30 | init(sourceUrl: URL, sourceContent: String) { 31 | self.sourceUrl = sourceUrl 32 | self.sourceContent = sourceContent 33 | super.init(id: TaskIds.astProducerTask.rawValue) 34 | } 35 | 36 | /// Execute the task and return the AST structure data model. 37 | /// 38 | /// - returns: The `AST` data model. 39 | /// - throws: Any error occurred during execution. 40 | override func execute() throws -> AST { 41 | let syntax = try Parser.parse(source: String(contentsOf: sourceUrl)) 42 | return AST(sourceHash: MD5(string: sourceContent), sourceFileSyntax: syntax, filePath: sourceUrl.path) 43 | } 44 | 45 | // MARK: - Private 46 | 47 | private let sourceUrl: URL 48 | private let sourceContent: String 49 | } 50 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/Tasks/ComponentInitsFilterTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceParsingFramework 20 | 21 | /// A task that checks the various aspects of a file, including if its 22 | /// content contains any component instantiations, to determine if the 23 | /// file should to be processed further. 24 | class ComponentInitsFilterTask: BaseFileFilterTask { 25 | 26 | /// Initializer. 27 | /// 28 | /// - parameter url: The file URL to read from. 29 | /// - parameter exclusionSuffixes: The list of file name suffixes to 30 | /// check from. If the given URL filename's suffix matches any in the 31 | /// this list, the URL will be excluded. 32 | /// - parameter exclusionPaths: The list of path components to check. 33 | /// If the given URL's path contains any elements in this list, the 34 | /// URL will be excluded. 35 | init(url: URL, exclusionSuffixes: [String], exclusionPaths: [String]) { 36 | super.init(url: url, exclusionSuffixes: exclusionSuffixes, exclusionPaths: exclusionPaths, taskId: TaskIds.componentInitsFilterTask.rawValue) 37 | } 38 | 39 | /// Create a set of filters for the given file content. 40 | /// 41 | /// - parameter content: The file content the returned filters should 42 | /// be applied on. 43 | /// - returns: A set of filters to use on the given content. 44 | override func filters(for content: String) -> [FileFilter] { 45 | return [ 46 | ComponentInitFilter(content: content) 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Parsing/Tasks/DeclarationsFilterTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import Foundation 19 | import SourceParsingFramework 20 | 21 | /// A task that checks the various aspects of a file, including if its 22 | /// content contains any component implementations, dependency protocol 23 | /// declarations or component instantiations, to determine if the file 24 | /// should to be processed further. 25 | class DeclarationsFilterTask: BaseFileFilterTask { 26 | 27 | /// Initializer. 28 | /// 29 | /// - parameter url: The file URL to read from. 30 | /// - parameter exclusionSuffixes: The list of file name suffixes to 31 | /// check from. If the given URL filename's suffix matches any in the 32 | /// this list, the URL will be excluded. 33 | /// - parameter exclusionPaths: The list of path components to check. 34 | /// If the given URL's path contains any elements in this list, the 35 | /// URL will be excluded. 36 | init(url: URL, exclusionSuffixes: [String], exclusionPaths: [String]) { 37 | super.init(url: url, exclusionSuffixes: exclusionSuffixes, exclusionPaths: exclusionPaths, taskId: TaskIds.declarationsFilterTask.rawValue) 38 | } 39 | 40 | /// Create a set of filters for the given file content. 41 | /// 42 | /// - parameter content: The file content the returned filters should 43 | /// be applied on. 44 | /// - returns: A set of filters to use on the given content. 45 | override func filters(for content: String) -> [FileFilter] { 46 | return [ 47 | BasicKeywordFilter(content: content) 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Utilities/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// The name of the NeedleFoundation module. 20 | let needleModuleName = "NeedleFoundation" 21 | 22 | /// The name of the bootstrap component an application's root component 23 | /// must inherit from. 24 | let bootstrapComponentName = "BootstrapComponent" 25 | 26 | /// The name of the protocol that all Dependency protocols inherit from. 27 | let dependencyProtocolName = "Dependency" 28 | 29 | /// The name of the class that all Component classes inherit from 30 | let componentClassName = "Component" 31 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Utilities/HashUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import SourceParsingFramework 19 | import var CommonCrypto.CC_MD5_DIGEST_LENGTH 20 | import func CommonCrypto.CC_MD5 21 | import typealias CommonCrypto.CC_LONG 22 | 23 | 24 | 25 | /// Calculates the MD5 hash of the input string 26 | func MD5(string: String) -> String { 27 | let length = Int(CC_MD5_DIGEST_LENGTH) 28 | let messageData = string.data(using:.utf8)! 29 | var digestData = Data(count: length) 30 | 31 | _ = digestData.withUnsafeMutableBytes { digestBytes -> UInt8 in 32 | messageData.withUnsafeBytes { messageBytes -> UInt8 in 33 | if let messageBytesBaseAddress = messageBytes.baseAddress, let digestBytesBlindMemory = digestBytes.bindMemory(to: UInt8.self).baseAddress { 34 | let messageLength = CC_LONG(messageData.count) 35 | CC_MD5(messageBytesBaseAddress, messageLength, digestBytesBlindMemory) 36 | } 37 | return 0 38 | } 39 | } 40 | 41 | return digestData.map( { String(format: "%02hhx", $0) }).joined() 42 | } 43 | 44 | /// Generates a cumulative hash of all the hashEntries 45 | func generateCumulativeHash(hashEntries: Set) -> String { 46 | let hashCollection = hashEntries.sorted().reduce(into: "") { 47 | (resultString, entry) in 48 | resultString.append(contentsOf: "\(entry.name):\(entry.hash)\n") 49 | } 50 | 51 | return MD5(string: hashCollection) 52 | } 53 | 54 | struct HashEntry: Hashable, Comparable { 55 | let name: String 56 | let hash: String 57 | 58 | static func < (lhs: HashEntry, rhs: HashEntry) -> Bool { 59 | return lhs.name < rhs.name 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Utilities/SourceKittenFramework/library_wrapper_sourcekitd.swift: -------------------------------------------------------------------------------- 1 | // Copied from SourceKittenFramework library_wrapper.swift [https://github.com/jpsim/SourceKitten/blob/master/Source/SourceKittenFramework/library_wrapper_sourcekitd.swift] 2 | // 3 | // library_wrapper.swift 4 | // sourcekitten 5 | // 6 | // Created by Norio Nomura on 2/20/16. 7 | // Copyright © 2016 SourceKitten. All rights reserved. 8 | // 9 | 10 | #if os(Linux) 11 | private let path = "libsourcekitdInProc.so" 12 | #else 13 | private let path = "sourcekitd.framework/Versions/A/sourcekitd" 14 | #endif 15 | private let library = toolchainLoader.load(path: path) 16 | internal let sourcekitd_initialize: @convention(c) () -> () = library.load(symbol: "sourcekitd_initialize") 17 | -------------------------------------------------------------------------------- /Generator/Sources/NeedleFramework/Utilities/TaskIds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// A list of IDs of task types used in the generator. 20 | enum TaskIds: Int { 21 | /// File filtering task IDs. 22 | case declarationsFilterTask = 1 23 | case pluginizedDeclarationsFilterTask = 2 24 | case componentExtensionsFilterTask = 13 25 | case componentInitsFilterTask = 14 26 | /// File content loading task ID. 27 | case fileContentLoaderTask = 3 28 | /// AST producing task ID. 29 | case astProducerTask = 4 30 | /// AST parsing task IDs. 31 | case declarationsParserTask = 5 32 | case componentExtenionsParserTask = 15 33 | case pluginizedDeclarationsParserTask = 6 34 | /// Dependency provider declaring task ID. 35 | case dependencyProviderDeclarerTask = 7 36 | /// Dependency provider content task IDs. 37 | case dependencyProviderContentTask = 8 38 | case pluginizedDependencyProviderContentTask = 9 39 | /// Dependency provider serialization task IDs. 40 | case dependencyProviderSerializerTask = 10 41 | case pluginizedDependencyProviderSerializerTask = 11 42 | /// Plugin extension serialization task ID. 43 | case pluginExtensionSerializerTask = 12 44 | } 45 | -------------------------------------------------------------------------------- /Generator/Sources/needle/Version.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | let version = "0.25.1" 18 | -------------------------------------------------------------------------------- /Generator/Sources/needle/Version.swift.template: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | let version = "__VERSION_NUMBER__" 18 | -------------------------------------------------------------------------------- /Generator/Sources/needle/VersionCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import CommandFramework 18 | import Foundation 19 | import NeedleFramework 20 | import TSCUtility 21 | 22 | /// A command that returns the current version of the generator. 23 | class VersionCommand: AbstractCommand { 24 | 25 | /// Initializer. 26 | /// 27 | /// - parameter parser: The argument parser to use. 28 | init(parser: ArgumentParser) { 29 | super.init(name: "version", overview: "The version of this generator.", parser: parser) 30 | } 31 | 32 | /// Execute the command. 33 | /// 34 | /// - parameter arguments: The command line arguments to execute the 35 | /// command with. 36 | override func execute(with arguments: ArgumentParser.Result) { 37 | print(version) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Generator/Sources/needle/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import TSCBasic 18 | import CommandFramework 19 | import Foundation 20 | import NeedleFramework 21 | import SourceParsingFramework 22 | import TSCUtility 23 | 24 | func main() { 25 | let parser = ArgumentParser(usage: " ", overview: "Needle DI code generator.") 26 | let commands = initializeCommands(with: parser) 27 | let inputs = Array(CommandLine.arguments.dropFirst()) 28 | do { 29 | let args = try parser.parse(inputs) 30 | execute(commands, with: parser, args) 31 | } catch (let e) { 32 | error("Command-line pasing error (use --help for help): \(e)") 33 | } 34 | } 35 | 36 | private func initializeCommands(with parser: ArgumentParser) -> [Command] { 37 | return [ 38 | VersionCommand(parser: parser), 39 | GenerateCommand(parser: parser), 40 | PrintDependencyTreeCommand(parser: parser) 41 | ] 42 | } 43 | 44 | private func execute(_ commands: [Command], with parser: ArgumentParser, _ args: ArgumentParser.Result) { 45 | if let subparserName = args.subparser(parser) { 46 | for command in commands { 47 | if subparserName == command.name { 48 | command.execute(with: args) 49 | } 50 | } 51 | } else { 52 | parser.printUsage(on: stdoutStream) 53 | } 54 | } 55 | 56 | main() 57 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Entry/GeneratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import SourceParsingFramework 19 | import XCTest 20 | @testable import NeedleFramework 21 | 22 | class GeneratorTests: XCTestCase { 23 | 24 | func test_generate_noThrow_verifySingleCall() { 25 | let generator = MockGenerator() 26 | 27 | XCTAssertEqual(generator.generateCallCount, 0) 28 | 29 | try! generator.generate(from: [], excludingFilesEndingWith: [], excludingFilesWithPaths: [], with: [], nil, to: "blah", shouldCollectParsingInfo: true, parsingTimeout: 10, exportingTimeout: 10, retryParsingOnTimeoutLimit: 1000, concurrencyLimit: nil, emitInputsDepsFile: false) 30 | 31 | XCTAssertEqual(generator.generateCallCount, 1) 32 | } 33 | } 34 | 35 | private class MockGenerator: Generator { 36 | 37 | fileprivate var generateCallCount = 0 38 | fileprivate var generateClosure: (() throws -> ())? = nil 39 | 40 | override func generate(from sourceRootUrls: [URL], withSourcesListFormat sourcesListFormatValue: String?, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], with additionalImports: [String], _ headerDocPath: String?, to destinationPath: String, using executor: SequenceExecutor, withParsingTimeout parsingTimeout: Double, exportingTimeout: Double, emitInputsDepsFile: Bool) throws { 41 | generateCallCount += 1 42 | try generateClosure?() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/ChildComponent.swift: -------------------------------------------------------------------------------- 1 | class MyChildComponent: Component { 2 | var book: Book { 3 | return shared { 4 | Book() 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/HasComponentExtensions.swift: -------------------------------------------------------------------------------- 1 | extension MyScope { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/HeaderDoc.txt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © Uber Technologies, Inc. All rights reserved. 3 | // 4 | // @generated by Needle 5 | // swiftlint:disable custom_rules -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/InvalidInits/InvalidInits1.swift: -------------------------------------------------------------------------------- 1 | class BParentComponent: Component { 2 | 3 | func newComp(param: Stuff) -> MyComponent { 4 | return MyComponent ( parent : param) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/InvalidInits/InvalidInits2.swift: -------------------------------------------------------------------------------- 1 | class CParentComponent: Component { 2 | 3 | var myComponent2: MyComponent2 { 4 | return MyComponent2 ( parent : self.blah) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/InvalidInits/InvalidInits3.swift: -------------------------------------------------------------------------------- 1 | let comp5 = My5Component ( parent : selfh) 2 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/InvalidInits/InvalidInits4.swift: -------------------------------------------------------------------------------- 1 | class DAnotherParentComponent: Component { 2 | 3 | func invalid(self2: Parent) -> MyComp6onent6 { 4 | return MyComp6onent6 ( parent : self2) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/InvalidInits/InvalidInits5.swift: -------------------------------------------------------------------------------- 1 | class RandomClass { 2 | 3 | var blah: Any? 4 | 5 | func someMethod() { 6 | return MyComp6onent6 ( parent : blah) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/NamespacedComponentSample.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RIBs; import Foundation 3 | 4 | protocol NamespacedDep: NeedleFoundation.Dependency { 5 | var blah: Blah { get } 6 | } 7 | 8 | class NamespacedComp: NeedleFoundation.Component { 9 | var donut: Donut { 10 | return Donut() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/NamespacedRootComponentSample.swift: -------------------------------------------------------------------------------- 1 | class NamespacedRootComp: NeedleFoundation.BootstrapComponent { 2 | var rootObj: Obj { 3 | return shared { 4 | Obj() 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/NonInheritanceComponent.swift: -------------------------------------------------------------------------------- 1 | class Component { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/NonNeedleComponent.swift: -------------------------------------------------------------------------------- 1 | class NonNeedleComponent: Component { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/NonSwift.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.diagnostic_stage" : "source.diagnostic.stage.swift.parse", 3 | "key.substructure" : [ 4 | { 5 | "key.bodyoffset" : 39, 6 | "key.nameoffset" : 6, 7 | "key.accessibility" : "source.lang.swift.accessibility.internal", 8 | "key.length" : 46, 9 | "key.elements" : [ 10 | { 11 | "key.kind" : "source.lang.swift.structure.elem.typeref", 12 | "key.offset" : 14, 13 | "key.length" : 23 14 | } 15 | ], 16 | "key.runtime_name" : "_TtC8__main__6MyComp", 17 | "key.name" : "MyComp", 18 | "key.inheritedtypes" : [ 19 | { 20 | "key.name" : "Component" 21 | } 22 | ], 23 | "key.kind" : "source.lang.swift.decl.class", 24 | "key.bodylength" : 6, 25 | "key.namelength" : 6, 26 | "key.offset" : 0 27 | } 28 | ], 29 | "key.offset" : 0, 30 | "key.length" : 47 31 | } 32 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/OnlyDependency.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RIBs; import Foundation 3 | 4 | protocol Only1Dependency: Dependency { 5 | var candy: Candy { get } 6 | var cheese: Cheese { get } 7 | } 8 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/Pluginized/NamespacedComponentSample.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RIBs; import Foundation 3 | 4 | class NamespacedNonCoreComponent: NeedleFoundation.NonCoreComponent {} 5 | 6 | protocol BExtension: NeedleFoundation.PluginExtension {} 7 | 8 | class SomePluginizedCompo2: NeedleFoundation.PluginizedComponent, Stuff { 9 | } 10 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/Pluginized/OnlyNonCoreComponent.swift: -------------------------------------------------------------------------------- 1 | import NeedleFoundation 2 | import ScoreSheet 3 | 4 | /// Component for the Game non core scope. 5 | public class GameNonCoreComponent: NeedleFoundation.NonCoreComponent { 6 | 7 | public var scoreSheetBuilder: ScoreSheetBuilder { 8 | return ScoreSheetComponent(parent: self) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/Pluginized/OnlyPluginExtension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RIBs; import Foundation 3 | 4 | protocol OnlyAPluginExtension: PluginExtension { 5 | var whatPluginPoint: WhatPluginPoint { get } 6 | } 7 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/Pluginized/OnlyPluginizedComponent.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RIBs; import Foundation 3 | 4 | class ANonCoreComponent: NonCoreComponent {} 5 | 6 | protocol BExtension: PluginExtension {} 7 | 8 | class SomePluginizedCompo: PluginizedComponent, Stuff { 9 | } 10 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/PrivateSample.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RIBs; import Foundation 3 | 4 | fileprivate protocol PrivateDependency: Dependency { 5 | var candy: Candy { get } 6 | var cheese: Cheese { get } 7 | } 8 | 9 | private class PrivateComponent: Component { 10 | 11 | private let stream: Stream = Stream() 12 | 13 | fileprivate var donut: Donut { 14 | return Donut() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/RootComponentSample.swift: -------------------------------------------------------------------------------- 1 | class NonNamespaceRootComp: BootstrapComponent { 2 | var rootObj: Obj { 3 | return shared { 4 | Obj() 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/ValidInits.swift: -------------------------------------------------------------------------------- 1 | class AParentComponent: Component { 2 | 3 | var myComponent: MyComponent { 4 | return MyComponent ( parent : self , x : X(y: 10) ) 5 | } 6 | 7 | var my1Component: My1Component { 8 | return My1Component ( parent : self 9 | ) 10 | } 11 | 12 | var myComponent2: MyComponent2 { 13 | return MyComponent2 ( parent : self) 14 | } 15 | } 16 | 17 | class AnotherParentComponent: Component { 18 | var myCompo3nent: MyCompo3nent { 19 | return MyCompo3nent ( parent : self, x : X(y: 10) ) 20 | } 21 | } 22 | 23 | let root = RootComponent(parent : BootstrapComponent()) 24 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/nocomp.swift: -------------------------------------------------------------------------------- 1 | class NoTemplate: Component { 2 | } 3 | 4 | class MisSpell: Comoinent { 5 | } 6 | 7 | class Some: Compnt { 8 | } 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/sample.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import Utility 3 | 4 | protocol SomeDependency: Dependency { 5 | var count: Integer { get } 6 | var name: String { get } 7 | var stream: Observable { get } 8 | } 9 | 10 | class SomeComponent: Component { 11 | let x: Int 12 | let y: String 13 | 14 | var name: String { 15 | return "slim shady" 16 | } 17 | } 18 | 19 | protocol OtherDependency: Dependency { 20 | // Comment 21 | var c: Integer { get } 22 | // More comments 23 | var s: String { get} 24 | } 25 | 26 | class OtherComponent: Component { 27 | let x: Int 28 | 29 | var name: String { 30 | return "max power" 31 | } 32 | } 33 | 34 | protocol IgnoreDependency { 35 | var total: Double { get } 36 | } 37 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Fixtures/yescomp.swift: -------------------------------------------------------------------------------- 1 | Component 2 | 3 | Comoinent 4 | 5 | Component 6 | 7 | 8 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Generating/AbstractGeneratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import XCTest 19 | @testable import NeedleFramework 20 | 21 | class AbstractGeneratorTests: XCTestCase { 22 | 23 | /// Retrieve the parsed component data models and import statements 24 | /// for the sample project. 25 | /// 26 | /// - returns: The list of component data models and import statements. 27 | func sampleProjectParsed() -> (components: [Component], imports: [String]) { 28 | let parser = DependencyGraphParser() 29 | let fixturesURL = sampleProjectUrl() 30 | let executor = MockSequenceExecutor() 31 | 32 | do { 33 | return try parser.parse(from: [fixturesURL], withSourcesListFormat: nil, excludingFilesEndingWith: ["ha", "yay", "blah"], using: executor, withTimeout: 10) 34 | } catch { 35 | fatalError("\(error)") 36 | } 37 | } 38 | 39 | /// Retrieve the URL for the sample project folder. 40 | /// 41 | /// - returns: The sample project folder URL. 42 | func sampleProjectUrl() -> URL { 43 | var url = URL(path: #file) 44 | for _ in 0 ..< 5 { 45 | url = url.deletingLastPathComponent() 46 | } 47 | url.appendPathComponent("Sample/MVC/TicTacToe/Sources/") 48 | 49 | let path = url.absoluteString.replacingOccurrences(of: "file://", with: "") 50 | return URL(path: path) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Generating/Pluginized/AbstractPluginizedGeneratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import XCTest 19 | @testable import NeedleFramework 20 | 21 | class AbstractPluginizedGeneratorTests: XCTestCase { 22 | 23 | /// Retrieve the parsed component data models and import statements 24 | /// for the sample project. 25 | /// 26 | /// - returns: The list of component data models, pluginized component 27 | /// data models and sorted import statements. 28 | func pluginizedSampleProjectParsed() -> ([Component], [PluginizedComponent], [String], String, Set) { 29 | let parser = PluginizedDependencyGraphParser() 30 | let fixturesURL = sampleProjectUrl() 31 | let executor = MockSequenceExecutor() 32 | 33 | do { 34 | return try parser.parse(from: [fixturesURL], withSourcesListFormat: nil, excludingFilesEndingWith: ["ha", "yay", "blah"], using: executor, withTimeout: 10) 35 | } catch { 36 | fatalError("\(error)") 37 | } 38 | } 39 | 40 | /// Retrieve the URL for the sample project folder. 41 | /// 42 | /// - returns: The sample project folder URL. 43 | func sampleProjectUrl() -> URL { 44 | var url = URL(fileURLWithPath: #file) 45 | for _ in 0 ..< 6 { 46 | url = url.deletingLastPathComponent() 47 | } 48 | url.appendPathComponent("Sample/Pluginized/TicTacToe/") 49 | 50 | let path = url.absoluteString.replacingOccurrences(of: "file://", with: "") 51 | return URL(path: path) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Parsing/ASTProducerTaskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import NeedleFramework 19 | import SwiftParser 20 | 21 | class ASTProducerTaskTests: AbstractParserTests { 22 | 23 | func test_execute_verifyNextTask() { 24 | let sourceUrl = fixtureUrl(for: "ComponentSample.swift") 25 | let sourceContent = try! String(contentsOf: sourceUrl) 26 | let astContent = Parser.parse(source: sourceContent) 27 | 28 | let task = ASTProducerTask(sourceUrl: sourceUrl, sourceContent: sourceContent) 29 | let result = try! task.execute() 30 | 31 | XCTAssertEqual(result.sourceFileSyntax.statements.count, astContent.statements.count) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Parsing/AbstractParserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | @testable import NeedleFramework 18 | import XCTest 19 | 20 | /// Base class for all parser related tests. 21 | class AbstractParserTests: XCTestCase { 22 | 23 | /// Retrieve the URL for a fixture file. 24 | /// 25 | /// - parameter file: The name of the file including extension. 26 | /// - returns: The fixture file URL. 27 | func fixtureUrl(for file: String) -> URL { 28 | return URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Fixtures/\(file)") 29 | } 30 | 31 | /// Retrieve the directory URL for the fixture folder. 32 | /// 33 | /// - returns: The fixture directory URL. 34 | func fixtureDirUrl() -> URL { 35 | let url = fixtureUrl(for: "") 36 | let path = url.absoluteString.replacingOccurrences(of: "file://", with: "") 37 | return URL(path: path) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Parsing/ComponentConsolidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SourceParsingFramework 18 | import XCTest 19 | @testable import NeedleFramework 20 | 21 | class ComponentConsolidatorTests: AbstractParserTests { 22 | 23 | func test_process_withMatchingSets_verifyResults() { 24 | let components = [ASTComponent(name: "A", dependencyProtocolName: "", isRoot: false, sourceHash: "AHash", filePath: "/tmp/A.swift", properties: [Property(name: "p1", type: "P1")], expressionCallTypeNames: ["E1"])] 25 | let componentExtensions = [ASTComponentExtension(name: "A", properties: [Property(name: "p2", type: "P2")], expressionCallTypeNames: ["E2"])] 26 | 27 | let processor = ComponentConsolidator(components: components, componentExtensions: componentExtensions) 28 | 29 | try! processor.process() 30 | 31 | XCTAssertEqual(components[0].properties, [Property(name: "p1", type: "P1"), Property(name: "p2", type: "P2")]) 32 | XCTAssertEqual(components[0].expressionCallTypeNames, ["E1", "E2"]) 33 | } 34 | 35 | func test_process_withNonMatchingSets_verifyError() { 36 | let componentExtensions = [ASTComponentExtension(name: "A", properties: [Property(name: "p2", type: "P2")], expressionCallTypeNames: ["E2"])] 37 | 38 | let processor = ComponentConsolidator(components: [], componentExtensions: componentExtensions) 39 | 40 | do { 41 | try processor.process() 42 | 43 | XCTFail() 44 | } catch { 45 | XCTAssertTrue(error is GenericError) 46 | XCTAssertTrue("\(error)".contains(componentExtensions[0].name)) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Parsing/ComponentExtensionFilterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import NeedleFramework 19 | 20 | class ComponentExtensionFilterTests: AbstractParserTests { 21 | 22 | func test_execute_hasComponentExtension_verifyFilter() { 23 | let fileUrl = fixtureUrl(for: "HasComponentExtensions.swift") 24 | let content = try! String(contentsOf: fileUrl) 25 | let filter = ComponentExtensionFilter(content: content, components: [ASTComponent(name: "MyScope", dependencyProtocolName: "", isRoot: false, sourceHash: "MyScopeHash", filePath: fileUrl.path, properties: [], expressionCallTypeNames: [])]) 26 | 27 | XCTAssertTrue(filter.filter()) 28 | } 29 | 30 | func test_execute_noExtensionNoComponent_verifyFilter() { 31 | let fileUrl = fixtureUrl(for: "nocomp.swift") 32 | let content = try! String(contentsOf: fileUrl) 33 | let filter = ComponentExtensionFilter(content: content, components: [ASTComponent(name: "MyComponent", dependencyProtocolName: "", isRoot: false, sourceHash: "MyComponentHash", filePath: fileUrl.path, properties: [], expressionCallTypeNames: [])]) 34 | 35 | XCTAssertFalse(filter.filter()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Parsing/DependencyLinkerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SourceParsingFramework 18 | import XCTest 19 | @testable import NeedleFramework 20 | 21 | class DependencyLinkerTests: AbstractParserTests { 22 | 23 | func test_process_withComponents_verifyLinkages() { 24 | let component = ASTComponent(name: "SomeComp", dependencyProtocolName: "ItsDependency", isRoot: false, sourceHash: "SomeCompHash", filePath: "/tmp/SomeComp.swift", properties: [], expressionCallTypeNames: []) 25 | let dependency = Dependency(name: "ItsDependency", properties: [], sourceHash: "ItsDependencyHash", filePath: "/tmp/ItsDependency.swift") 26 | 27 | let linker = DependencyLinker(components: [component], dependencies: [dependency]) 28 | 29 | try! linker.process() 30 | 31 | XCTAssertEqual(component.dependencyProtocol, dependency) 32 | } 33 | 34 | func test_process_withComponentsNoDependency_verifyError() { 35 | let component = ASTComponent(name: "SomeComp", dependencyProtocolName: "ItsDependency", isRoot: false, sourceHash: "SomeComp", filePath: "/tmp/SomeComp.swift", properties: [], expressionCallTypeNames: []) 36 | let dependency = Dependency(name: "WrongDep", properties: [], sourceHash: "WrongDepHash", filePath: "/tmp/WrongDep.swift") 37 | 38 | let linker = DependencyLinker(components: [component], dependencies: [dependency]) 39 | 40 | do { 41 | try linker.process() 42 | XCTFail() 43 | } catch GenericError.withMessage(_) { 44 | // Success. 45 | } catch { 46 | XCTFail() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Parsing/ParentLinkerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import NeedleFramework 19 | 20 | class ParentLinkerTests: AbstractParserTests { 21 | 22 | func test_process_withComponents_verifyLinkages() { 23 | let parentComponent = ASTComponent(name: "ParentComp", dependencyProtocolName: "Doesn't matter", isRoot: true, sourceHash: "ParentCompHash", filePath: "/tmp/ParentComp.swift", properties: [], expressionCallTypeNames: ["ChildComp", "someOtherStuff"]) 24 | let childComp = ASTComponent(name: "ChildComp", dependencyProtocolName: "Still doesn't matter", isRoot: false, sourceHash: "ChildCompHash", filePath: "/tmp/ChildComp.swift", properties: [], expressionCallTypeNames: []) 25 | 26 | let linker = ParentLinker(components: [parentComponent, childComp]) 27 | try! linker.process() 28 | 29 | XCTAssertEqual(childComp.parents.count, 1) 30 | XCTAssertTrue(childComp.parents[0] === parentComponent) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/AbstractPluginizedParserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | @testable import NeedleFramework 18 | import XCTest 19 | 20 | /// Base class for all pluginized parser related tests. 21 | class AbstractPluginizedParserTests: AbstractParserTests { 22 | 23 | /// Retrieve the URL for a pluginized fixture file. 24 | /// 25 | /// - parameter file: The name of the file including extension. 26 | /// - returns: The fixture file URL. 27 | func pluginizedFixtureUrl(for file: String) -> URL { 28 | return URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Fixtures/Pluginized/\(file)") 29 | } 30 | 31 | /// Retrieve the directory URL for the fixture folder. 32 | /// 33 | /// - returns: The fixture directory URL. 34 | func pluginizedFixtureDirUrl() -> URL { 35 | let url = fixtureUrl(for: "") 36 | let path = url.absoluteString.replacingOccurrences(of: "file://", with: "") 37 | return URL(path: path) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Generator/Tests/NeedleFrameworkTests/Utilities/HashUtilsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Concurrency 18 | import SourceParsingFramework 19 | import XCTest 20 | @testable import NeedleFramework 21 | 22 | class HashUtilsTests: XCTestCase { 23 | 24 | func test_generate_md5_hash_for_a_string() { 25 | let expectedHashString = "5d41402abc4b2a76b9719d911017c592" // Generated by executing "md5 -s "hello" 26 | XCTAssertEqual(expectedHashString, MD5(string: "hello")) 27 | } 28 | 29 | func test_generate_hash_of_hash_entries() { 30 | let hashEntries : Set = [ HashEntry(name: "bbb", hash: "second-string-hash"), 31 | HashEntry(name: "aaa", hash: "first-string-hash"), 32 | HashEntry(name: "ccc", hash: "third-string-hash") 33 | ] 34 | let hash = generateCumulativeHash(hashEntries: hashEntries) 35 | let generatedString = "aaa:first-string-hash\nbbb:second-string-hash\nccc:third-string-hash\n" 36 | XCTAssertEqual(MD5(string: generatedString), hash) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Generator/bin/lib_InternalSwiftSyntaxParser.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/needle/2a79817bc25d162eef2362f82f0fe2b005cbd3b0/Generator/bin/lib_InternalSwiftSyntaxParser.dylib -------------------------------------------------------------------------------- /Generator/bin/needle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/needle/2a79817bc25d162eef2362f82f0fe2b005cbd3b0/Generator/bin/needle -------------------------------------------------------------------------------- /Generator/xcode.xcconfig: -------------------------------------------------------------------------------- 1 | OTHER_SWIFT_FLAGS[config=Debug] = -DDEBUG 2 | -------------------------------------------------------------------------------- /Images/build_phases.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/needle/2a79817bc25d162eef2362f82f0fe2b005cbd3b0/Images/build_phases.jpeg -------------------------------------------------------------------------------- /Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/needle/2a79817bc25d162eef2362f82f0fe2b005cbd3b0/Images/logo.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINARY_FOLDER_PREFIX?=/usr/local 2 | GENERATOR_FOLDER=Generator 3 | GENERATOR_ARCHIVE_PATH=$(shell cd $(GENERATOR_FOLDER) && swift build $(SWIFT_BUILD_FLAGS) --show-bin-path)/needle 4 | GENERATOR_VERSION_FOLDER_PATH=$(GENERATOR_FOLDER)/Sources/needle 5 | GENERATOR_VERSION_FILE_PATH=$(GENERATOR_VERSION_FOLDER_PATH)/Version.swift 6 | SWIFT_BUILD_FLAGS=--disable-sandbox -c release --arch arm64 --arch x86_64 7 | 8 | .PHONY: clean build install uninstall 9 | 10 | clean: 11 | cd $(GENERATOR_FOLDER) && swift package clean 12 | 13 | build: 14 | cd $(GENERATOR_FOLDER) && swift build $(SWIFT_BUILD_FLAGS) 15 | 16 | install: uninstall archive_generator 17 | 18 | uninstall: 19 | rm -f "$(BINARY_FOLDER_PREFIX)/bin/needle" 20 | rm -f "/usr/local/bin/needle" 21 | 22 | release: 23 | git checkout master 24 | $(eval NEW_VERSION := $(filter-out $@, $(MAKECMDGOALS))) 25 | @sed 's/__VERSION_NUMBER__/$(NEW_VERSION)/g' $(GENERATOR_VERSION_FOLDER_PATH)/Version.swift.template > $(GENERATOR_VERSION_FILE_PATH) 26 | %: 27 | @: 28 | sed -i '' "s/\(s.version.*=.*'\).*\('\)/\1$(NEW_VERSION)\2/" NeedleFoundation.podspec 29 | make archive_generator 30 | git add $(GENERATOR_FOLDER)/bin/needle 31 | git add $(GENERATOR_VERSION_FILE_PATH) 32 | git add NeedleFoundation.podspec 33 | $(eval NEW_VERSION_TAG := v$(NEW_VERSION)) 34 | git commit -m "Update generator binary and version file for $(NEW_VERSION_TAG)" 35 | git push origin master 36 | git tag $(NEW_VERSION_TAG) 37 | git push origin $(NEW_VERSION_TAG) 38 | 39 | publish: 40 | brew update && brew bump-formula-pr --tag=$(shell git describe --tags) --revision=$(shell git rev-parse HEAD) needle 41 | pod trunk push --allow-warnings 42 | 43 | archive_generator: build 44 | mv $(GENERATOR_ARCHIVE_PATH) $(GENERATOR_FOLDER)/bin/ 45 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Needle depends on the following libraries: 2 | 3 | SourceKitten (https://github.com/jpsim/SourceKitten) 4 | Swift Package Manager (https://github.com/apple/swift-package-manager) 5 | Swift Concurrency (https://github.com/uber/swift-concurrency) 6 | RxSwift (https://github.com/ReactiveX/RxSwift) 7 | SnapKit (https://github.com/SnapKit/SnapKit) 8 | 9 | Copyright (C) 2017 The Guava Authors 10 | 11 | Licensed under the Apache License, Version 2.0 (the "License"); 12 | you may not use this file except in compliance with the License. 13 | You may obtain a copy of the License at 14 | 15 | http://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, 19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | See the License for the specific language governing permissions and 21 | limitations under the License. 22 | -------------------------------------------------------------------------------- /NeedleFoundation.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'NeedleFoundation' 3 | s.version = '0.25.1' 4 | s.summary = 'Compile-time safe Swift dependency injection framework with real code.' 5 | s.description = 'Needle is a dependency injection (DI) system for Swift. Unlike other DI frameworks, such as Cleanse, Swinject, Needle encourages hierarchical DI structure and utilizes code generation to ensure compile-time safety. This allows us to develop our apps and make changes with confidence. If it compiles, it works. In this aspect, Needle is more similar to Android Dagger.' 6 | 7 | s.homepage = 'https://github.com/uber/needle' 8 | s.license = { :type => 'Apache 2.0', :file => 'LICENSE.txt' } 9 | s.author = { 'Yi Wang' => 'yiw@uber.com' } 10 | 11 | s.source = { :git => 'https://github.com/uber/needle.git', :tag => "v" + s.version.to_s } 12 | s.source_files = 'Sources/**/*.swift' 13 | s.ios.deployment_target = '9.0' 14 | s.swift_versions = ['5.2', '5.3', '5.4', '5.5', '5.6'] 15 | end 16 | -------------------------------------------------------------------------------- /NeedleFoundation.xcodeproj/NeedleFoundationTestTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /NeedleFoundation.xcodeproj/NeedleFoundationTest_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /NeedleFoundation.xcodeproj/NeedleFoundationTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /NeedleFoundation.xcodeproj/NeedleFoundation_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /NeedleFoundation.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NeedleFoundation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /NeedleFoundation.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.10 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "NeedleFoundation", 6 | products: [ 7 | .library(name: "NeedleFoundation", targets: ["NeedleFoundation"]), 8 | .library(name: "NeedleFoundationTest", targets: ["NeedleFoundationTest"]) 9 | ], 10 | dependencies: [], 11 | targets: [ 12 | .target( 13 | name: "NeedleFoundation", 14 | dependencies: []), 15 | .testTarget( 16 | name: "NeedleFoundationTests", 17 | dependencies: ["NeedleFoundation"], 18 | exclude: []), 19 | .target( 20 | name: "NeedleFoundationTest", 21 | dependencies: ["NeedleFoundation"]), 22 | .testTarget( 23 | name: "NeedleFoundationTestTests", 24 | dependencies: ["NeedleFoundationTest"], 25 | exclude: []), 26 | ], 27 | swiftLanguageVersions: [.v5] 28 | ) 29 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Publishing a New Release 2 | *This page contains instructions for admins of this project to release a new version.* 3 | 4 | ## Run Makefile 5 | 1. Run `$ make release VERSION_NUMBER` at the root directory of needle (where the `Makefile` is located) 6 | 7 | For example: 8 | ``` 9 | $ make release 0.13.0 10 | ``` 11 | 12 | 2. Run `$ make publish` to publish the release to various destinations (like CocoaPods and Homebrew). 13 | 14 | ## Create a new Github release 15 | After all the steps in the `Makefile` finish successfully, go to the Releases tab and create a new release. 16 | -------------------------------------------------------------------------------- /Sample/MVC/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import UIKit 19 | 20 | @UIApplicationMain 21 | class AppDelegate: UIResponder, UIApplicationDelegate { 22 | 23 | var window: UIWindow? 24 | 25 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 26 | registerProviderFactories() 27 | 28 | let window = UIWindow(frame: UIScreen.main.bounds) 29 | self.window = window 30 | 31 | let rootComponent = RootComponent() 32 | window.rootViewController = rootComponent.rootViewController 33 | 34 | window.makeKeyAndVisible() 35 | return true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/Game/GameComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import UIKit 19 | 20 | protocol GameDependency: Dependency { 21 | var mutableScoreStream: MutableScoreStream { get } 22 | var playersStream: PlayersStream { get } 23 | } 24 | 25 | class GameComponent: Component, GameBuilder { 26 | 27 | var gameViewController: UIViewController { 28 | return GameViewController(mutableScoreStream: dependency.mutableScoreStream, playersStream: dependency.playersStream, scoreSheetBuilder: scoreSheetBuilder) 29 | } 30 | 31 | var scoreSheetBuilder: ScoreSheetBuilder { 32 | return ScoreSheetComponent(parent: self) 33 | } 34 | 35 | // This should not be used as the provider for GameDependency. 36 | var mutableScoreStream: MutableScoreStream { 37 | return ScoreStreamImpl() 38 | } 39 | } 40 | 41 | // Use a builder protocol to allow mocking for unit tests. At the same time, 42 | // this allows GameViewController to be initialized lazily. 43 | protocol GameBuilder { 44 | var gameViewController: UIViewController { get } 45 | } 46 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import UIKit 19 | 20 | protocol LoggedInDependency: Dependency { 21 | 22 | } 23 | 24 | class LoggedInComponent: Component, LoggedInBuilder { 25 | 26 | public var scoreStream: ScoreStream { 27 | return mutableScoreStream 28 | } 29 | 30 | var loggedInViewController: UIViewController { 31 | return LoggedInViewController(gameBuilder: gameComponent, scoreStream: scoreStream, scoreSheetBuilder: scoreSheetComponent) 32 | } 33 | 34 | var gameComponent: GameComponent { 35 | return GameComponent(parent: self) 36 | } 37 | 38 | var scoreSheetComponent: ScoreSheetComponent { 39 | return ScoreSheetComponent(parent: self) 40 | } 41 | } 42 | 43 | // Use a builder protocol to allow mocking for unit tests. At the same time, 44 | // this allows LoggedInViewController to be initialized lazily. 45 | protocol LoggedInBuilder { 46 | var loggedInViewController: UIViewController { get } 47 | } 48 | 49 | // Use extension to show parsing of component extensions. 50 | extension LoggedInComponent { 51 | 52 | var mutableScoreStream: MutableScoreStream { 53 | return shared { ScoreStreamImpl() } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import UIKit 19 | 20 | protocol LoggedOutDependency: Dependency { 21 | var mutablePlayersStream: MutablePlayersStream { get } 22 | } 23 | 24 | class LoggedOutComponent: Component, LoggedOutBuilder { 25 | 26 | var loggedOutViewController: UIViewController { 27 | return LoggedOutViewController(mutablePlayersStream: dependency.mutablePlayersStream) 28 | } 29 | } 30 | 31 | // Use a builder protocol to allow mocking for unit tests. At the same time, 32 | // this allows LoggedOutViewController to be initialized lazily. 33 | protocol LoggedOutBuilder { 34 | var loggedOutViewController: UIViewController { get } 35 | } 36 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/Root/PlayersStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import RxSwift 19 | 20 | protocol PlayersStream { 21 | var names: Observable<(String, String)?> { get } 22 | } 23 | 24 | protocol MutablePlayersStream: PlayersStream { 25 | func update(player1: String?, player2: String?) 26 | } 27 | 28 | class PlayersStreamImpl: MutablePlayersStream { 29 | 30 | private let subject = BehaviorSubject<(String, String)?>(value: nil) 31 | 32 | var names: Observable<(String, String)?> { 33 | return subject.asObservable() 34 | } 35 | 36 | func update(player1: String?, player2: String?) { 37 | let player1Name: String 38 | if let player1 = player1, !player1.isEmpty { 39 | player1Name = player1 40 | } else { 41 | player1Name = "Player 1" 42 | } 43 | let player2Name: String 44 | if let player2 = player2, !player2.isEmpty { 45 | player2Name = player2 46 | } else { 47 | player2Name = "Player 2" 48 | } 49 | subject.onNext((player1Name, player2Name)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/Root/RootComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import UIKit 19 | 20 | class RootComponent: BootstrapComponent { 21 | 22 | public var playersStream: PlayersStream { 23 | return mutablePlayersStream 24 | } 25 | 26 | public var mutablePlayersStream: MutablePlayersStream { 27 | return shared { PlayersStreamImpl() } 28 | } 29 | 30 | var rootViewController: UIViewController { 31 | return RootViewController(playersStream: playersStream, loggedOutBuilder: loggedOutComponent, loggedInBuilder: loggedInComponent) 32 | } 33 | 34 | var loggedOutComponent: LoggedOutComponent { 35 | return LoggedOutComponent(parent: self) 36 | } 37 | 38 | var loggedInComponent: LoggedInComponent { 39 | return LoggedInComponent(parent: self) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import RxSwift 19 | import UIKit 20 | 21 | protocol ScoreSheetDependency: Dependency { 22 | var scoreStream: ScoreStream { get } 23 | } 24 | 25 | class ScoreSheetComponent: Component, ScoreSheetBuilder { 26 | 27 | var scoreSheetViewController: UIViewController { 28 | return ScoreSheetViewController(scoreStream: dependency.scoreStream) 29 | } 30 | } 31 | 32 | // Use a builder protocol to allow mocking for unit tests 33 | protocol ScoreSheetBuilder { 34 | var scoreSheetViewController: UIViewController { get } 35 | } 36 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/Tests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sample/MVC/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sample/Pluginized/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/ScoreSheet/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 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/ScoreSheet/ScoreSheetComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import RxSwift 19 | import UIKit 20 | 21 | public protocol ScoreSheetDependency: Dependency { 22 | var scoreStream: ScoreStream { get } 23 | } 24 | 25 | public class ScoreSheetComponent: Component, ScoreSheetBuilder { 26 | 27 | public var scoreSheetViewController: UIViewController { 28 | return ScoreSheetViewController(scoreStream: dependency.scoreStream) 29 | } 30 | } 31 | 32 | // Use a builder protocol to allow mocking for unit tests 33 | public protocol ScoreSheetBuilder { 34 | var scoreSheetViewController: UIViewController { get } 35 | } 36 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/ScoreSheetTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/ScoreSheetTests/ScoreSheetTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import ScoreSheet 19 | 20 | class ScoreSheetTests: XCTestCase { 21 | 22 | override func setUp() { 23 | super.setUp() 24 | // Put setup code here. This method is called before the invocation of each test method in the class. 25 | } 26 | 27 | override func tearDown() { 28 | // Put teardown code here. This method is called after the invocation of each test method in the class. 29 | super.tearDown() 30 | } 31 | 32 | func testExample() { 33 | // This is an example of a functional test case. 34 | // Use XCTAssert and related functions to verify your tests produce the correct results. 35 | } 36 | 37 | func testPerformanceExample() { 38 | // This is an example of a performance test case. 39 | self.measure { 40 | // Put the code you want to measure the time of here. 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import UIKit 19 | 20 | @UIApplicationMain 21 | class AppDelegate: UIResponder, UIApplicationDelegate { 22 | 23 | var window: UIWindow? 24 | 25 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 26 | registerProviderFactories() 27 | 28 | let window = UIWindow(frame: UIScreen.main.bounds) 29 | self.window = window 30 | 31 | let rootComponent = RootComponent() 32 | window.rootViewController = rootComponent.rootViewController 33 | 34 | window.makeKeyAndVisible() 35 | 36 | return true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/LoggedOut/LoggedOutComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import UIKit 19 | 20 | protocol LoggedOutDependency: Dependency { 21 | var mutablePlayersStream: MutablePlayersStream { get } 22 | } 23 | 24 | class LoggedOutComponent: Component, LoggedOutBuilder { 25 | 26 | var loggedOutViewController: UIViewController { 27 | return LoggedOutViewController(mutablePlayersStream: dependency.mutablePlayersStream) 28 | } 29 | } 30 | 31 | // Use a builder protocol to allow mocking for unit tests. At the same time, 32 | // this allows LoggedOutViewController to be initialized lazily. 33 | protocol LoggedOutBuilder { 34 | var loggedOutViewController: UIViewController { get } 35 | } 36 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/ObservableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import RxSwift 18 | import UIKit 19 | 20 | /// The lifecycle of a view controller. 21 | enum ViewControllerLifecycle { 22 | case viewDidAppear 23 | case viewDidDisappear 24 | case `deinit` 25 | } 26 | 27 | /// A view controller whose lifecycle events can be observed via Rx. 28 | class ObservableViewController: UIViewController { 29 | 30 | /// The lifecycle observable of this view controller. 31 | var lifecycle: Observable { 32 | return lifecycleSubject.asObservable() 33 | } 34 | 35 | override func viewDidAppear(_ animated: Bool) { 36 | super.viewDidAppear(animated) 37 | 38 | lifecycleSubject.onNext(.viewDidAppear) 39 | } 40 | 41 | override func viewDidDisappear(_ animated: Bool) { 42 | super.viewDidDisappear(animated) 43 | 44 | lifecycleSubject.onNext(.viewDidDisappear) 45 | } 46 | 47 | // MARK: - Private 48 | 49 | private let lifecycleSubject = ReplaySubject.create(bufferSize: 1) 50 | 51 | deinit { 52 | lifecycleSubject.onNext(.deinit) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/PluginizedScopeLifecycle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import NeedleFoundation 19 | import RxSwift 20 | import UIKit 21 | 22 | extension ObservableViewController: PluginizedScopeLifecycleObservable { 23 | 24 | public func observe(_ observer: @escaping (PluginizedScopeLifecycle) -> Void) -> ObserverDisposable { 25 | let disposable = lifecycle 26 | .subscribe(onNext: { (event: ViewControllerLifecycle) in 27 | switch event { 28 | case .deinit: 29 | observer(.deinit) 30 | case .viewDidAppear: 31 | observer(.active) 32 | case .viewDidDisappear: 33 | observer(.inactive) 34 | } 35 | }) 36 | return NeedleDisposable(disposable: disposable) 37 | } 38 | } 39 | 40 | private class NeedleDisposable: ObserverDisposable { 41 | 42 | private let disposable: Disposable 43 | 44 | fileprivate init(disposable: Disposable) { 45 | self.disposable = disposable 46 | } 47 | 48 | fileprivate func dispose() { 49 | disposable.dispose() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/Root/PlayersStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import RxSwift 19 | 20 | protocol PlayersStream { 21 | var names: Observable<(String, String)?> { get } 22 | } 23 | 24 | protocol MutablePlayersStream: PlayersStream { 25 | func update(player1: String?, player2: String?) 26 | } 27 | 28 | class PlayersStreamImpl: MutablePlayersStream { 29 | 30 | private let subject = BehaviorSubject<(String, String)?>(value: nil) 31 | 32 | var names: Observable<(String, String)?> { 33 | return subject.asObservable() 34 | } 35 | 36 | func update(player1: String?, player2: String?) { 37 | let player1Name: String 38 | if let player1 = player1, !player1.isEmpty { 39 | player1Name = player1 40 | } else { 41 | player1Name = "Player 1" 42 | } 43 | let player2Name: String 44 | if let player2 = player2, !player2.isEmpty { 45 | player2Name = player2 46 | } else { 47 | player2Name = "Player 2" 48 | } 49 | subject.onNext((player1Name, player2Name)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/Root/RootComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import UIKit 19 | 20 | class RootComponent: BootstrapComponent { 21 | 22 | public var playersStream: PlayersStream { 23 | return mutablePlayersStream 24 | } 25 | 26 | public var mutablePlayersStream: MutablePlayersStream { 27 | return shared { PlayersStreamImpl() } 28 | } 29 | 30 | var rootViewController: UIViewController { 31 | return RootViewController(playersStream: playersStream, loggedOutBuilder: loggedOutComponent, loggedInBuilder: loggedInComponent) 32 | } 33 | 34 | var loggedOutComponent: LoggedOutComponent { 35 | return LoggedOutComponent(parent: self) 36 | } 37 | 38 | var loggedInComponent: LoggedInComponent { 39 | return LoggedInComponent(parent: self) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCore/UberPluginizedComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | 19 | // PluginizedComponent subclass for adding additional behaviour to PluginizedComponent 20 | // Exists in this repo for Needle Generator testing purposes 21 | open class UberPluginizedComponent: PluginizedComponent {} 22 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeCoreTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeIntegrations/GameNonCoreComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import ScoreSheet 19 | 20 | public protocol GameNonCoreDependency: Dependency { 21 | var mutableScoreStream: MutableScoreStream { get } 22 | } 23 | 24 | /// Component for the Game non core scope. 25 | public class GameNonCoreComponent: NonCoreComponent { 26 | 27 | public var scoreSheetBuilder: ScoreSheetBuilder { 28 | return ScoreSheetComponent(parent: self) 29 | } 30 | 31 | // This should not be used as the provider for GameNonCoreDependency. 32 | var mutableScoreStream: MutableScoreStream { 33 | return ScoreStreamImpl() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeIntegrations/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 | -------------------------------------------------------------------------------- /Sample/Pluginized/TicTacToe/TicTacToeIntegrations/LoggedInNonCoreComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import ScoreSheet 19 | 20 | /// Component for the LoggedIn non core scope. 21 | public class LoggedInNonCoreComponent: NonCoreComponent { 22 | 23 | public var scoreSheetBuilder: ScoreSheetBuilder { 24 | return ScoreSheetComponent(parent: self) 25 | } 26 | 27 | public var mutableScoreStream: MutableScoreStream { 28 | return shared { ScoreStreamImpl() } 29 | } 30 | 31 | public var scoreStream: ScoreStream { 32 | return mutableScoreStream 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sample/README.md: -------------------------------------------------------------------------------- 1 | # Sample App Using Needle 2 | 3 | The folder "MVC" contains the simple MVC architecture based TicTacToe app. The folder "Pluginized" contains the same TicTacToe app but built with a pluginized DI structure where the dependencies are divided into separate core and non-core trees. 4 | 5 | ## Build & Run TicTacToe 6 | 7 | Open the TicTacToe.xcodeproj to build and run the game. 8 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import UIKit 19 | import SwiftUI 20 | 21 | @UIApplicationMain 22 | class AppDelegate: UIResponder, UIApplicationDelegate { 23 | 24 | var window: UIWindow? 25 | 26 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 27 | registerProviderFactories() 28 | 29 | let window = UIWindow(frame: UIScreen.main.bounds) 30 | self.window = window 31 | 32 | let rootComponent = RootComponent() 33 | window.rootViewController = UIHostingController(rootView: rootComponent.rootView) 34 | 35 | window.makeKeyAndVisible() 36 | return true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/Game/GameComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import SwiftUI 19 | 20 | protocol GameDependency: Dependency { 21 | var mutableScoreStream: MutableScoreStream { get } 22 | var playersStream: PlayersStream { get } 23 | } 24 | 25 | class GameComponent: Component, GameBuilder { 26 | 27 | var gameViewModel: GameViewModel { 28 | GameViewModel( 29 | mutableScoreStream: dependency.mutableScoreStream, 30 | playersStream: dependency.playersStream 31 | ) 32 | } 33 | 34 | var gameView: AnyView { 35 | AnyView( 36 | GameView( 37 | viewModel: gameViewModel, 38 | scoreSheetBuilder: scoreSheetBuilder 39 | ) 40 | ) 41 | } 42 | 43 | var scoreSheetBuilder: ScoreSheetBuilder { 44 | ScoreSheetComponent(parent: self) 45 | } 46 | 47 | // This should not be used as the provider for GameDependency. 48 | var mutableScoreStream: MutableScoreStream { 49 | ScoreStreamImpl() 50 | } 51 | } 52 | 53 | // Use a builder protocol to allow mocking for unit tests. At the same time, 54 | // this allows GameView to be initialized lazily. 55 | protocol GameBuilder { 56 | var gameView: AnyView { get } 57 | } 58 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/GridStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SwiftUI 18 | 19 | struct GridStack: View { 20 | let rows: Int 21 | let columns: Int 22 | let spacing: CGFloat? 23 | let content: (Int, Int) -> Content 24 | 25 | var body: some View { 26 | VStack(spacing: spacing) { 27 | ForEach(0 ..< rows, id: \.self) { row in 28 | HStack(spacing: spacing) { 29 | ForEach(0 ..< columns, id: \.self) { column in 30 | content(row, column) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | init( 38 | rows: Int, 39 | columns: Int, 40 | spacing: CGFloat? = nil, 41 | @ViewBuilder content: @escaping (Int, Int) -> Content 42 | ) { 43 | self.rows = rows 44 | self.columns = columns 45 | self.spacing = spacing 46 | self.content = content 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedIn/LoggedInComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import SwiftUI 19 | 20 | protocol LoggedInDependency: Dependency { 21 | 22 | } 23 | 24 | class LoggedInComponent: Component, LoggedInBuilder { 25 | 26 | public var scoreStream: ScoreStream { 27 | return mutableScoreStream 28 | } 29 | 30 | var loggedInViewModel: LoggedInViewModel { 31 | LoggedInViewModel(scoreStream: scoreStream) 32 | } 33 | 34 | var loggedInView: AnyView { 35 | AnyView( 36 | LoggedInView( 37 | viewModel: loggedInViewModel, 38 | scoreSheetBuilder: scoreSheetComponent, 39 | gameBuilder: gameComponent 40 | ) 41 | ) 42 | } 43 | 44 | var gameComponent: GameComponent { 45 | return GameComponent(parent: self) 46 | } 47 | 48 | var scoreSheetComponent: ScoreSheetComponent { 49 | return ScoreSheetComponent(parent: self) 50 | } 51 | } 52 | 53 | // Use a builder protocol to allow mocking for unit tests. At the same time, 54 | // this allows LoggedInView to be initialized lazily. 55 | protocol LoggedInBuilder { 56 | var loggedInView: AnyView { get } 57 | } 58 | 59 | // Use extension to show parsing of component extensions. 60 | extension LoggedInComponent { 61 | var mutableScoreStream: MutableScoreStream { 62 | return shared { ScoreStreamImpl() } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedIn/LoggedInView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SwiftUI 18 | 19 | struct LoggedInView: View where ViewModel: LoggedInViewModelProtocol { 20 | @ObservedObject var viewModel: ViewModel 21 | let scoreSheetBuilder: ScoreSheetBuilder 22 | let gameBuilder: GameBuilder 23 | 24 | var body: some View { 25 | ZStack { 26 | Color.yellow.edgesIgnoringSafeArea(.all) 27 | VStack(alignment: .center) { 28 | NavigationLink( 29 | destination: gameBuilder.gameView, 30 | tag: Screen.game.rawValue, 31 | selection: $viewModel.selection 32 | ) { 33 | EmptyView() 34 | } 35 | NavigationLink( 36 | destination: scoreSheetBuilder.scoreSheetView, 37 | tag: Screen.score.rawValue, 38 | selection: $viewModel.selection 39 | ) { 40 | EmptyView() 41 | } 42 | Button("Play TicTacToe") { 43 | viewModel.gameTapped() 44 | } 45 | .padding() 46 | .frame(maxWidth: .infinity) 47 | .background(Color.black) 48 | .foregroundColor(.white) 49 | Button("High Scores") { 50 | viewModel.scoreTapped() 51 | } 52 | .frame(maxWidth: .infinity) 53 | .padding() 54 | .background(Color.black) 55 | .foregroundColor(.white) 56 | } 57 | .padding() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedIn/LoggedInViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import Combine 19 | 20 | protocol LoggedInViewModelProtocol: ObservableObject { 21 | var selection: String? { get set } 22 | func gameTapped() 23 | func scoreTapped() 24 | } 25 | 26 | final class LoggedInViewModel: LoggedInViewModelProtocol { 27 | private let scoreStream: ScoreStream 28 | private var gameCancellable: Cancellable? 29 | @Published var selection: String? = nil 30 | 31 | init(scoreStream: ScoreStream) { 32 | self.scoreStream = scoreStream 33 | } 34 | 35 | func scoreTapped() { 36 | selection = Screen.score.rawValue 37 | } 38 | 39 | func gameTapped() { 40 | selection = Screen.game.rawValue 41 | gameCancellable?.cancel() 42 | gameCancellable = scoreStream.gameDidEnd 43 | .prefix(1) 44 | .sink(receiveValue: { _ in 45 | // TODO: dismiss 46 | }) 47 | } 48 | 49 | deinit { 50 | gameCancellable?.cancel() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedOut/LoggedOutComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import SwiftUI 19 | 20 | protocol LoggedOutDependency: Dependency { 21 | var mutablePlayersStream: MutablePlayersStream { get } 22 | } 23 | 24 | class LoggedOutComponent: Component, LoggedOutBuilder { 25 | 26 | var loggedOutViewModel: LoggedOutViewModel { 27 | LoggedOutViewModel( 28 | mutablePlayersStream: dependency.mutablePlayersStream 29 | ) 30 | } 31 | 32 | var loggedOutView: AnyView { 33 | return AnyView( 34 | LoggedOutView(viewModel: loggedOutViewModel) 35 | ) 36 | } 37 | } 38 | 39 | // Use a builder protocol to allow mocking for unit tests. At the same time, 40 | // this allows LoggedOutView to be initialized lazily. 41 | protocol LoggedOutBuilder { 42 | var loggedOutView: AnyView { get } 43 | } 44 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedOut/LoggedOutView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SwiftUI 18 | 19 | struct LoggedOutView: View where ViewModel: LoggedOutViewModelProtocol { 20 | @ObservedObject var viewModel: ViewModel 21 | 22 | var body: some View { 23 | VStack(alignment: .center) { 24 | TextField("Player 1", text: $viewModel.player1) 25 | .textFieldStyle(RoundedBorderTextFieldStyle()) 26 | TextField("Player 2", text: $viewModel.player2) 27 | .textFieldStyle(RoundedBorderTextFieldStyle()) 28 | Button("Login") { 29 | viewModel.login() 30 | hideKeyboard() 31 | } 32 | .frame(maxWidth: .infinity) 33 | .padding() 34 | .background(Color.black) 35 | .foregroundColor(.white) 36 | .padding(.top) 37 | } 38 | .padding() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/LoggedOut/LoggedOutViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | protocol LoggedOutViewModelProtocol: ObservableObject { 20 | var player1: String { get set } 21 | var player2: String { get set } 22 | func login() 23 | } 24 | 25 | final class LoggedOutViewModel: LoggedOutViewModelProtocol { 26 | @Published var player1: String = "" 27 | @Published var player2: String = "" 28 | 29 | private let mutablePlayersStream: MutablePlayersStream 30 | 31 | init(mutablePlayersStream: MutablePlayersStream) { 32 | self.mutablePlayersStream = mutablePlayersStream 33 | } 34 | 35 | func login() { 36 | mutablePlayersStream.update(player1: player1, player2: player2) 37 | } 38 | } 39 | 40 | enum Screen: String { 41 | case game 42 | case score 43 | } 44 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/Root/PlayersStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import Combine 19 | 20 | typealias Opponents = (String, String) 21 | 22 | protocol PlayersStream { 23 | var names: AnyPublisher { get } 24 | } 25 | 26 | protocol MutablePlayersStream: PlayersStream { 27 | func update(player1: String?, player2: String?) 28 | } 29 | 30 | class PlayersStreamImpl: MutablePlayersStream { 31 | 32 | private let subject: CurrentValueSubject = CurrentValueSubject(nil) 33 | 34 | var names: AnyPublisher { 35 | return subject.eraseToAnyPublisher() 36 | } 37 | 38 | func update(player1: String?, player2: String?) { 39 | let player1Name: String 40 | if let player1 = player1, !player1.isEmpty { 41 | player1Name = player1 42 | } else { 43 | player1Name = "Player 1" 44 | } 45 | let player2Name: String 46 | if let player2 = player2, !player2.isEmpty { 47 | player2Name = player2 48 | } else { 49 | player2Name = "Player 2" 50 | } 51 | subject.send((player1Name, player2Name)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/Root/RootComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import SwiftUI 19 | 20 | class RootComponent: BootstrapComponent { 21 | 22 | public var playersStream: PlayersStream { 23 | return mutablePlayersStream 24 | } 25 | 26 | public var mutablePlayersStream: MutablePlayersStream { 27 | return shared { PlayersStreamImpl() } 28 | } 29 | 30 | var rootViewModel: RootViewModel { 31 | RootViewModel(playerStream: playersStream) 32 | } 33 | 34 | var rootView: some View { 35 | NavigationView { 36 | RootView( 37 | viewModel: rootViewModel, 38 | loggedOutBuilder: loggedOutComponent, 39 | loggedInBuilder: loggedInComponent 40 | ) 41 | } 42 | } 43 | 44 | var loggedOutComponent: LoggedOutComponent { 45 | return LoggedOutComponent(parent: self) 46 | } 47 | 48 | var loggedInComponent: LoggedInComponent { 49 | return LoggedInComponent(parent: self) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/Root/RootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SwiftUI 18 | 19 | struct RootView: View where ViewModel: RootViewModelProtocol { 20 | @ObservedObject var viewModel: ViewModel 21 | let loggedOutBuilder: LoggedOutBuilder 22 | let loggedInBuilder: LoggedInBuilder 23 | 24 | var body: some View { 25 | switch viewModel.state { 26 | case .loggedOut: 27 | loggedOutBuilder.loggedOutView 28 | case .loggedIn: 29 | loggedInBuilder.loggedInView 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/Root/RootViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import Combine 19 | 20 | protocol RootViewModelProtocol: ObservableObject { 21 | var state: RootChildStates { get } 22 | } 23 | 24 | final class RootViewModel: RootViewModelProtocol { 25 | @Published var state: RootChildStates = .loggedOut 26 | private let playerStream: PlayersStream 27 | private var playersStreamCancellable: Cancellable? 28 | 29 | init(playerStream: PlayersStream) { 30 | self.playerStream = playerStream 31 | updateFeature() 32 | } 33 | 34 | func updateFeature() { 35 | state = .loggedOut 36 | if playersStreamCancellable != nil { 37 | return 38 | } 39 | 40 | playersStreamCancellable = playerStream.names 41 | .map { (names: (String, String)?) in 42 | names == nil ? RootChildStates.loggedOut : RootChildStates.loggedIn 43 | } 44 | .removeDuplicates() 45 | .sink(receiveValue: { [weak self] (state: RootChildStates) in 46 | guard let strongSelf = self else { 47 | return 48 | } 49 | strongSelf.state = state 50 | }) 51 | } 52 | 53 | deinit { 54 | playersStreamCancellable?.cancel() 55 | } 56 | } 57 | 58 | enum RootChildStates { 59 | case loggedOut 60 | case loggedIn 61 | } 62 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/ScoreSheet/ScoreSheetComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import NeedleFoundation 18 | import SwiftUI 19 | 20 | protocol ScoreSheetDependency: Dependency { 21 | var scoreStream: ScoreStream { get } 22 | } 23 | 24 | class ScoreSheetComponent: Component, ScoreSheetBuilder { 25 | 26 | var scoreSheetViewModel: ScoreSheetViewModel { 27 | ScoreSheetViewModel( 28 | scoreStream: dependency.scoreStream 29 | ) 30 | } 31 | 32 | var scoreSheetView: AnyView { 33 | return AnyView( 34 | ScoreSheetView( 35 | viewModel: scoreSheetViewModel 36 | ) 37 | ) 38 | } 39 | } 40 | 41 | // Use a builder protocol to allow mocking for unit tests 42 | protocol ScoreSheetBuilder { 43 | var scoreSheetView: AnyView { get } 44 | } 45 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/ScoreSheet/ScoreSheetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SwiftUI 18 | 19 | struct ScoreSheetView: View where ViewModel: ScoreSheetViewModelProtocol { 20 | @Environment(\.presentationMode) var presentationMode 21 | @ObservedObject var viewModel: ViewModel 22 | 23 | var body: some View { 24 | ZStack { 25 | Color.blue.edgesIgnoringSafeArea(.all) 26 | VStack(alignment: .center) { 27 | Text(viewModel.player1Score) 28 | .frame(height: 44) 29 | .frame(maxWidth: .infinity) 30 | .background(Color.gray) 31 | .padding() 32 | Text(viewModel.player2Score) 33 | .frame(height: 44) 34 | .frame(maxWidth: .infinity) 35 | .background(Color.gray) 36 | .padding() 37 | Button("OK") { 38 | presentationMode.wrappedValue.dismiss() 39 | } 40 | .frame(height: 44) 41 | .frame(maxWidth: .infinity) 42 | .background(Color.black) 43 | .padding() 44 | .foregroundColor(Color.white) 45 | } 46 | .navigationBarBackButtonHidden(true) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/ScoreSheet/ScoreSheetViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import Combine 19 | 20 | protocol ScoreSheetViewModelProtocol: ObservableObject { 21 | var player1Score: String { get } 22 | var player2Score: String { get } 23 | } 24 | 25 | final class ScoreSheetViewModel: ScoreSheetViewModelProtocol { 26 | private let scoreStream: ScoreStream 27 | private var cancellables = Set() 28 | @Published var player1Score: String = "None : 0" 29 | @Published var player2Score: String = "None : 0" 30 | 31 | init(scoreStream: ScoreStream) { 32 | self.scoreStream = scoreStream 33 | setupScoreStream() 34 | } 35 | 36 | func setupScoreStream() { 37 | let initial = ( 38 | PlayerScore(name: "None", score: 0), 39 | PlayerScore(name: "None", score: 0) 40 | ) 41 | 42 | scoreStream.scores 43 | .prepend(initial) 44 | .map { score1, _ in 45 | "\(score1.name) : \(score1.score)" 46 | } 47 | .assign(to: \.player1Score, on: self) 48 | .store(in: &cancellables) 49 | 50 | scoreStream.scores 51 | .prepend(initial) 52 | .map { _, score2 in 53 | "\(score2.name) : \(score2.score)" 54 | } 55 | .assign(to: \.player2Score, on: self) 56 | .store(in: &cancellables) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Sources/View+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import SwiftUI 18 | 19 | #if canImport(UIKit) 20 | extension View { 21 | func hideKeyboard() { 22 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 23 | } 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/Tests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sample/SwiftUI-MVVM/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sample/copyright_header.txt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // -------------------------------------------------------------------------------- /Sources/NeedleFoundation/Pluginized/PluginizedScopeLifecycle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /// The lifecycle of a pluginized scope. This represents the lifecycle of 20 | /// the scope that utilizes a pluginized DI component. In the case of an 21 | /// iOS MVC application, the lifecycle events should be mapped to the view 22 | /// controller lifecycles. 23 | public enum PluginizedScopeLifecycle { 24 | /// The active lifecycle. This can be represented as a view controller's 25 | /// `viewDidAppear` lifecycle. 26 | case active 27 | /// The inactivate lifecycle. This can be represented as a view 28 | /// controller's `viewDidDisappear` lifecycle. 29 | case inactive 30 | /// The deinit lifecycle. This can be represented as a view controller's 31 | /// `deinit` lifecycle. 32 | case `deinit` 33 | } 34 | 35 | /// The object that allows an observer to be disposed, thereby ending the 36 | /// observation. 37 | public protocol ObserverDisposable: AnyObject { 38 | 39 | /// Dispose the observation. 40 | func dispose() 41 | } 42 | 43 | /// The observable of the lifecycle events of a pluginized scope. 44 | public protocol PluginizedScopeLifecycleObservable: AnyObject { 45 | 46 | /// Observe the lifecycle events with given observer. 47 | /// 48 | /// - parameter observer: The observer closure to invoke when the lifecycle 49 | /// changes. 50 | /// - returns: The disposable object that can end the observation. 51 | func observe(_ observer: @escaping (PluginizedScopeLifecycle) -> Void) -> ObserverDisposable 52 | } 53 | -------------------------------------------------------------------------------- /Tests/NeedleFoundationTests/ComponentTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import NeedleFoundation 19 | 20 | class ComponentTests: XCTestCase { 21 | 22 | override func setUp() { 23 | super.setUp() 24 | 25 | let path = "^->TestComponent" 26 | __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: path) { component in 27 | return EmptyDependencyProvider.init(component: component) 28 | } 29 | } 30 | 31 | func test_shared_veirfySingleInstance() { 32 | let component = TestComponent() 33 | XCTAssert(component.share === component.share, "Should have returned same shared object") 34 | 35 | XCTAssertTrue(component.share2 === component.share2) 36 | XCTAssertFalse(component.share === component.share2) 37 | } 38 | 39 | func test_shared_optional() { 40 | let component = TestComponent() 41 | XCTAssert(component.optionalShare === component.expectedOptionalShare) 42 | } 43 | } 44 | 45 | class TestComponent: BootstrapComponent { 46 | 47 | fileprivate var callCount: Int = 0 48 | fileprivate var expectedOptionalShare: ClassProtocol? = { 49 | return ClassProtocolImpl() 50 | }() 51 | 52 | var share: NSObject { 53 | callCount += 1 54 | return shared { NSObject() } 55 | } 56 | 57 | var share2: NSObject { 58 | return shared { NSObject() } 59 | } 60 | 61 | fileprivate var optionalShare: ClassProtocol? { 62 | return shared { self.expectedOptionalShare } 63 | } 64 | } 65 | 66 | private protocol ClassProtocol: AnyObject { 67 | 68 | } 69 | 70 | private class ClassProtocolImpl: ClassProtocol { 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Tests/NeedleFoundationTests/DependencyProviderRegistryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018. Uber Technologies 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import XCTest 18 | @testable import NeedleFoundation 19 | 20 | class DependencyProviderRegistryTests: XCTestCase { 21 | 22 | override func setUp() { 23 | super.setUp() 24 | 25 | let path = "^->MockAppComponent" 26 | __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: path) { component in 27 | return EmptyDependencyProvider(component: component) 28 | } 29 | } 30 | 31 | func test_registerProviderFactory_verifyRetrievingProvider_verifyDependencyReference() { 32 | let expectedProvider = MockRootDependencyProvider() 33 | 34 | let path = "^->MockAppComponent->MockRootComponent" 35 | __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: path) { (component: Scope) -> AnyObject in 36 | return expectedProvider 37 | } 38 | 39 | let appComponent = MockAppComponent() 40 | let actualProvider = __DependencyProviderRegistry.instance.dependencyProvider(for: appComponent.rootComponent) 41 | 42 | XCTAssertTrue(expectedProvider === actualProvider) 43 | XCTAssertTrue(appComponent.rootComponent.dependency === expectedProvider) 44 | } 45 | } 46 | 47 | class MockAppComponent: BootstrapComponent { 48 | 49 | var rootComponent: MockRootComponent { 50 | return MockRootComponent(parent: self) 51 | } 52 | } 53 | 54 | protocol MockRootDependency: AnyObject {} 55 | 56 | class MockRootComponent: Component {} 57 | 58 | class MockRootDependencyProvider: MockRootDependency {} 59 | -------------------------------------------------------------------------------- /foundation.xcconfig: -------------------------------------------------------------------------------- 1 | IPHONEOS_DEPLOYMENT_TARGET = 9.0 2 | --------------------------------------------------------------------------------