├── .github └── workflows │ ├── Format.yml │ └── Test.yml ├── .gitignore ├── .swiftformat ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── Scripts ├── format.sh └── install_swift-format.sh ├── Sources ├── DeclarativeConfiguration │ └── Exports.swift ├── FunctionalBuilder │ ├── Builder.swift │ └── BuilderProvider.swift ├── FunctionalClosures │ ├── DataSource.swift │ ├── Deprecations │ │ └── Assign.swift │ └── Handler.swift ├── FunctionalConfigurator │ ├── ConfigIntializable.swift │ ├── Configurator.swift │ ├── CustomConfigurable.swift │ └── Modification.swift ├── FunctionalKeyPath │ └── FunctionalKeyPath.swift └── FunctionalModification │ ├── Deprecations │ └── Modification.swift │ └── Reduce.swift └── Tests └── DeclarativeConfigurationTests ├── BuilderTests.swift ├── ConfiguratorTests.swift ├── FunctionalClosuresTests.swift └── ModificationTests.swift /.github/workflows/Format.yml: -------------------------------------------------------------------------------- 1 | name: format 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | format: 10 | if: | 11 | !contains(github.event.head_commit.message, '[ci skip]') && 12 | !contains(github.event.head_commit.message, '[ci skip format]') 13 | name: swift-format 14 | runs-on: macos-12 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Install 18 | run: make install_formatter 19 | - name: Format 20 | run: make format 21 | - uses: stefanzweifel/git-auto-commit-action@v4 22 | with: 23 | commit_message: 'style(ci): run swiftformat' 24 | branch: 'main' 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | -------------------------------------------------------------------------------- /.github/workflows/Test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test_macos: 11 | if: | 12 | !contains(github.event.head_commit.message, '[ci skip]') && 13 | !contains(github.event.head_commit.message, '[ci skip test]') && 14 | !contains(github.event.head_commit.message, '[ci skip test_macos]') 15 | runs-on: macOS-12 16 | timeout-minutes: 30 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Select Xcode 14.1.0 20 | run: sudo xcode-select -s /Applications/Xcode_14.1.0.app 21 | - name: Run tests 22 | run: make test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | Scripts/.installed 7 | Scripts/.bin 8 | DerivedData 9 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --acronyms ID,URL,URI,UUID,ULID,SPM,XML,YAML,JSON,IDFA 2 | --allman false 3 | --assetliterals visual-width 4 | --binarygrouping none 5 | --categorymark "MARK: %c" 6 | --classthreshold 0 7 | --closingparen balanced 8 | --closurevoid remove 9 | --commas always 10 | --conflictmarkers reject 11 | --decimalgrouping none 12 | --elseposition same-line 13 | --emptybraces no-space 14 | --enumnamespaces always 15 | --enumthreshold 0 16 | --exponentcase lowercase 17 | --exponentgrouping disabled 18 | --extensionacl on-declarations 19 | --extensionlength 0 20 | --extensionmark "MARK: - %t + %c" 21 | --fractiongrouping disabled 22 | --fragment false 23 | --funcattributes preserve 24 | --generictypes 25 | --groupedextension "MARK: %c" 26 | --guardelse auto 27 | --header ignore 28 | --hexgrouping none 29 | --hexliteralcase uppercase 30 | --ifdef indent 31 | --importgrouping alpha 32 | --indent 2 33 | --indentcase false 34 | --indentstrings false 35 | --lifecycle 36 | --lineaftermarks true 37 | --linebreaks lf 38 | --markcategories true 39 | --markextensions always 40 | --marktypes always 41 | --maxwidth none 42 | --modifierorder 43 | --nevertrailing 44 | --nospaceoperators 45 | --nowrapoperators 46 | --octalgrouping none 47 | --operatorfunc spaced 48 | --organizetypes actor,class,enum,struct 49 | --patternlet hoist 50 | --ranges spaced 51 | --redundanttype infer-locals-only 52 | --self init-only 53 | --selfrequired 54 | --semicolons never 55 | --shortoptionals always 56 | --smarttabs enabled 57 | --someAny true 58 | --stripunusedargs always 59 | --structthreshold 0 60 | --tabwidth unspecified 61 | --trailingclosures 62 | --trimwhitespace always 63 | --typeattributes preserve 64 | --typeblanklines remove 65 | --typemark "MARK: - %t" 66 | --varattributes preserve 67 | --voidtype void 68 | --wraparguments before-first 69 | --wrapcollections before-first 70 | --wrapconditions preserve 71 | --wrapparameters before-first 72 | --wrapreturntype preserve 73 | --wrapternary default 74 | --wraptypealiases preserve 75 | --xcodeindentation disabled 76 | --yodaswap always 77 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Maxim Krouk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install_formatter: 2 | @chmod +x ./scripts/install_swift-format.sh 3 | @./scripts/install_swift-format.sh 4 | 5 | update_formatter: 6 | @rm ./scripts/.bin/swift-format 7 | @make install_formatter 8 | 9 | format: 10 | @chmod +x ./scripts/format.sh 11 | @./scripts/format.sh 12 | 13 | test: 14 | @swift test --enable-test-discovery 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.6 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "swift-declarative-configuration", 7 | platforms: [ 8 | .iOS(.v11), 9 | .macOS(.v10_13), 10 | .tvOS(.v11), 11 | .macCatalyst(.v13), 12 | .watchOS(.v4), 13 | ], 14 | products: [ 15 | .library( 16 | name: "DeclarativeConfiguration", 17 | targets: ["DeclarativeConfiguration"] 18 | ), 19 | .library( 20 | name: "FunctionalBuilder", 21 | targets: ["FunctionalBuilder"] 22 | ), 23 | .library( 24 | name: "FunctionalConfigurator", 25 | targets: ["FunctionalConfigurator"] 26 | ), 27 | .library( 28 | name: "FunctionalClosures", 29 | targets: ["FunctionalClosures"] 30 | ), 31 | .library( 32 | name: "FunctionalKeyPath", 33 | targets: ["FunctionalKeyPath"] 34 | ), 35 | .library( 36 | name: "FunctionalModification", 37 | targets: ["FunctionalModification"] 38 | ), 39 | ], 40 | targets: [ 41 | .target( 42 | name: "DeclarativeConfiguration", 43 | dependencies: [ 44 | .target(name: "FunctionalBuilder"), 45 | .target(name: "FunctionalConfigurator"), 46 | .target(name: "FunctionalClosures"), 47 | .target(name: "FunctionalKeyPath"), 48 | .target(name: "FunctionalModification"), 49 | ] 50 | ), 51 | .target( 52 | name: "FunctionalBuilder", 53 | dependencies: [ 54 | .target(name: "FunctionalConfigurator"), 55 | .target(name: "FunctionalKeyPath"), 56 | .target(name: "FunctionalModification"), 57 | ] 58 | ), 59 | .target( 60 | name: "FunctionalConfigurator", 61 | dependencies: [ 62 | .target(name: "FunctionalKeyPath"), 63 | .target(name: "FunctionalModification"), 64 | ] 65 | ), 66 | .target(name: "FunctionalClosures"), 67 | .target( 68 | name: "FunctionalKeyPath", 69 | dependencies: [ 70 | .target(name: "FunctionalModification"), 71 | ] 72 | ), 73 | .target(name: "FunctionalModification"), 74 | .testTarget( 75 | name: "DeclarativeConfigurationTests", 76 | dependencies: [ 77 | .target(name: "DeclarativeConfiguration"), 78 | ] 79 | ), 80 | ] 81 | ) 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Declarative Configuration 2 | 3 | [![Test](https://github.com/CaptureContext/swift-declarative-configuration/actions/workflows/Test.yml/badge.svg)](https://github.com/CaptureContext/swift-declarative-configuration/actions/workflows/Test.yml) [![SwiftPM 5.6](https://img.shields.io/badge/swiftpm-5.6-ED523F.svg?style=flat)](https://swift.org/download/) ![Platforms](https://img.shields.io/badge/platforms-iOS_11_|_macOS_10.13_|_tvOS_11_|_watchOS_4_|_Catalyst_13-ED523F.svg?style=flat) [![@capture_context](https://img.shields.io/badge/contact-@capture__context-1DA1F2.svg?style=flat&logo=twitter)](https://twitter.com/capture_context) 4 | 5 | Swift Declarative Configuration (SDC, for short) is a tiny library, that enables you to configure your objects in a declarative, consistent and understandable way, with ergonomics in mind. It can be used to configure any objects on any platform, including server-side-swift. 6 | 7 | ## Products 8 | 9 | - **[FunctionalModification](./Sources/FunctionalModification)** 10 | 11 | Provides modification functions for copying and modifying immutable stuff. It is useful for self-configuring objects like builder, when modifying methods should return modified `self`. 12 | 13 | - **[FunctionalKeyPath](./Sources/FunctionalKeyPath)** 14 | 15 | Functional KeyPath wrapper. 16 | 17 | - **[FunctionalConfigurator](./Sources/FunctionalConfigurator)** 18 | 19 | Functional configurator for anything, enables you to specify modification of an object and to apply the modification later. 20 | 21 | Also contains self-implementing protocols (`ConfigInitializable`, `CustomConfigurable`) to enable you add custom configuration support for your types (`NSObject` already conforms to it for you). 22 | 23 | - **[FunctionalBuilder](./Sources/FunctionalBuilder)** 24 | 25 | Functional builder for anything, enables you to modify object instances in a declarative way. Also contains `BuilderProvider` protocol with a computed `builder` property and implements that protocol on `NSObject` type. 26 | 27 | - **[FunctionalClosures](./Sources/FunctionalClosures)** 28 | 29 | Functional closures allow you to setup functional handlers & datasources, the API may seem a bit strange at the first look, so feel free to ask or discuss anything [here](https://github.com/MakeupStudio/swift-declarative-configuration/issues/1). 30 | 31 | - **[DeclarativeConfiguration](./Sources/DeclarativeConfiguration)** 32 | 33 | Wraps and exports all the products. 34 | 35 | ## Basic Usage 36 | 37 | > **See tests for more** 38 | 39 | ### No SDC 40 | 41 | ```swift 42 | class ImageViewController: UIViewController { 43 | let imageView: UIImageView = { 44 | let imageView = UIImageView() 45 | imageView.contentMode = .scaleAspectFit 46 | imageView.backgroundColor = .black 47 | imageView.layer.masksToBounds = true 48 | imageView.layer.cornerRadius = 10 49 | return imageView 50 | }() 51 | 52 | override func loadView() { 53 | self.view = imageView 54 | } 55 | } 56 | ``` 57 | 58 | ### FunctionalConfigurator 59 | 60 | > **Note:** This way is **recommended**, but remember, that custom types **MUST** implement initializer with no parameters even if the superclass already has it or you will get a crash otherwise. 61 | 62 | ```swift 63 | import FunctionalConfigurator 64 | 65 | class ImageViewController: UIViewController { 66 | let imageView = UIImageView { $0 67 | .contentMode(.scaleAspectFit) 68 | .backgroundColor(.black) 69 | .layer.scope { $0 70 | .masksToBounds(true) 71 | .cornerRadius(10) 72 | } 73 | } 74 | 75 | override func loadView() { 76 | self.view = imageView 77 | } 78 | } 79 | ``` 80 | 81 | ### FunctionalBuilder 82 | 83 | > **Note:** This way is recommended too, and it is more **safe**, because it modifies existing objects. 84 | 85 | ```swift 86 | import FunctionalBuilder 87 | 88 | class ImageViewController: UIViewController { 89 | let imageView = UIImageView().builder 90 | .contentMode(.scaleAspectFit) 91 | .backgroundColor(.black) 92 | .layer.masksToBounds(true) 93 | .layer.cornerRadius(10) 94 | .build() 95 | 96 | override func loadView() { 97 | self.view = imageView 98 | } 99 | } 100 | ``` 101 | 102 | ### FunctionalClosures 103 | 104 | ### No SDC 105 | 106 | **Declaration** 107 | 108 | ```swift 109 | public class TapGestureRecognizer: UITapGestureRecognizer { 110 | var onTapGesture: ((TapGestureRecognizer) -> Void)? 111 | 112 | init() { 113 | super.init(target: nil, action: nil) 114 | commonInit() 115 | } 116 | 117 | override public init(target: Any?, action: Selector?) { 118 | super.init(target: target, action: action) 119 | commonInit() 120 | } 121 | 122 | private func commonInit() { 123 | self.addTarget(self, action: #selector(handleTap)) 124 | } 125 | 126 | @objc private func handleTap(_ recognizer: TapGestureRecognizer) { 127 | onTapGesture?(recognizer) 128 | } 129 | } 130 | ``` 131 | 132 | **Usage** 133 | 134 | ```swift 135 | let tapRecognizer = TapGestureRecognizer() 136 | 137 | // handler setup 138 | tapRecognizer.onTapGesture = { recognizer in 139 | // ... 140 | } 141 | 142 | // call from the outside 143 | tapRecognizer.onTapGesture?(tapRecognizer) 144 | ``` 145 | 146 | ### With SDC 147 | 148 | **Declaration** 149 | 150 | ```swift 151 | public class TapGestureRecognizer: UITapGestureRecognizer { 152 | @Handler 153 | var onTapGesture 154 | 155 | init() { 156 | super.init(target: nil, action: nil) 157 | commonInit() 158 | } 159 | 160 | override public init(target: Any?, action: Selector?) { 161 | super.init(target: target, action: action) 162 | commonInit() 163 | } 164 | 165 | private func commonInit() { 166 | self.addTarget(self, action: #selector(handleTap)) 167 | } 168 | 169 | @objc private func handleTap(_ recognizer: TapGestureRecognizer) { 170 | _onTapGesture(recognizer) 171 | } 172 | } 173 | ``` 174 | 175 | **Usage** 176 | 177 | ```swift 178 | let tapRecognizer = TapGestureRecognizer() 179 | 180 | // handler setup now called as function 181 | tapRecognizer.onTapGesture { recognizer in 182 | // ... 183 | } 184 | 185 | // call from the outside now uses propertyWrapper projectedValue API, which is not as straitforward 186 | // and it is nice, because: 187 | // - handlers usually should not be called from the outside 188 | // - you do not lose the ability to call it, but an API tells you that it's kinda private 189 | tapRecognizer.$onTapGesture?(tapRecognizer) 190 | ``` 191 | 192 | Also you can create such an instance with `Configurator`: 193 | 194 | ```swift 195 | let tapRecognizer = TapGestureRecognizer { $0 196 | .$onTapGesture { recognizer in 197 | // ... 198 | } 199 | } 200 | ``` 201 | 202 | ### More 203 | 204 | #### Builder 205 | 206 | Customize any object by passing initial value to a builder 207 | 208 | ```swift 209 | let object = Builder(Object()) 210 | .property.subproperty(value) 211 | .build() // Returns modified object 212 | ``` 213 | 214 | For classes you can avoid returning a value by calling `apply` method, instead of `build` 215 | 216 | ```swift 217 | let _class = _Class() 218 | Builder(_class) 219 | .property.subproperty(value) 220 | .apply() // Returns Void 221 | ``` 222 | 223 | In both Builders and Configurators you can use scoping 224 | 225 | ```swift 226 | let object = Object { $0 227 | .property.subproperty(value) 228 | } 229 | ``` 230 | 231 | 232 | 233 | Conform your own types to `BuilderProvider` protocol to access builder property. 234 | 235 | ```swift 236 | import CoreLocation 237 | import DeclarativeConfiguration 238 | 239 | extension CLLocationCoordinate2D: BuilderProvider {} 240 | // Now you can access `location.builder.latitude(0).build()` 241 | ``` 242 | 243 | #### Configurator 244 | 245 | > **Note:** Your NSObject classes **must** implement `init()` to use Configurators. It's a little trade-off for the convenience it brings to your codebase, see [tests](./Tests/DeclarativeConfigurationTests/ConfiguratorTests.swift) for an example. 246 | 247 | #### DataSource 248 | 249 | `OptionalDataSource` and `DataSource` types are very similar to the `Handler`, but if `Handler` is kinda `OptionalDataSource`, the second one may have different types of an output. Usage is similar, different types are provided just for better semantics. 250 | 251 | ## Installation 252 | 253 | ### Basic 254 | 255 | You can add DeclarativeConfiguration to an Xcode project by adding it as a package dependency. 256 | 257 | 1. From the **File** menu, select **Swift Packages › Add Package Dependency…** 258 | 2. Enter [`"https://github.com/makeupstudio/swift-declarative-configuration"`](https://github.com/makeupstudio/swift-declarative-configuration) into the package repository URL text field 259 | 3. Choose products you need to link them to your project. 260 | 261 | ### Recommended 262 | 263 | If you use SwiftPM for your project structure, add DeclarativeConfiguration to your package file. 264 | 265 | ```swift 266 | .package( 267 | url: "git@github.com:capturecontext/swift-declarative-configuration.git", 268 | .upToNextMinor(from: "0.3.0") 269 | ) 270 | ``` 271 | or via HTTPS 272 | 273 | ```swift 274 | .package( 275 | url: "https://github.com:capturecontext/swift-declarative-configuration.git", 276 | .exact("0.3.0") 277 | ) 278 | ``` 279 | 280 | Do not forget about target dependencies: 281 | 282 | ```swift 283 | .product( 284 | name: "DeclarativeConfiguration", 285 | package: "swift-declarative-configuration" 286 | ) 287 | ``` 288 | 289 | ## License 290 | 291 | This library is released under the MIT license. See [LICENSE](./LICENSE) for details. 292 | -------------------------------------------------------------------------------- /Scripts/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | _get_parent_dir_abs_path() { 4 | echo "$(cd "$(dirname "$1")" && pwd)" 5 | } 6 | 7 | # ––––––––––––––––––––––––– Constants –––––––––––––––––––––––––– 8 | 9 | SCRIPT_DIR=$(_get_parent_dir_abs_path $0) 10 | TOOLS_DIR="${SCRIPT_DIR}/.bin/" 11 | TOOL_NAME="swiftformat" 12 | TOOL="${TOOLS_DIR}/${TOOL_NAME}" 13 | 14 | # ––––––––––––––––––––––––––– Script ––––––––––––––––––––––––––– 15 | 16 | cd ${SCRIPT_DIR} 17 | cd .. 18 | 19 | ${TOOL} . \ 20 | --config .swiftformat 21 | -------------------------------------------------------------------------------- /Scripts/install_swift-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | _get_parent_dir_abs_path() { 4 | echo "$(cd "$(dirname "$1")" && pwd)" 5 | } 6 | 7 | # ––––––––––––––––––––––––––– Config ––––––––––––––––––––––––––– 8 | 9 | TOOL_NAME="swiftformat" 10 | TOOL_OWNER="nicklockwood" 11 | TOOL_VERSION="0.50.5" 12 | 13 | # ––––––––––––––––––––––––– Constants –––––––––––––––––––––––––– 14 | 15 | SCRIPT_DIR=$(_get_parent_dir_abs_path $0) 16 | TOOLS_INSTALL_PATH="${SCRIPT_DIR}/.bin" 17 | TOOL_INSTALL_PATH="${TOOLS_INSTALL_PATH}/${TOOL_NAME}" 18 | TOOL_DOWNLOAD_DIR="${TOOLS_INSTALL_PATH}/_${TOOL_NAME}" 19 | 20 | TOOL=${TOOL_INSTALL_PATH} 21 | TOOL_REPO="https://github.com/${TOOL_OWNER}/${TOOL_NAME}" 22 | ARCHIVE_NAME="${TOOL_NAME}.artifactbundle" 23 | ARCHIVE_URL="${TOOL_REPO}/releases/download/${TOOL_VERSION}/${ARCHIVE_NAME}.zip" 24 | 25 | # ––––––––––––––––––––––––––– Steps –––––––––––––––––––––––––––– 26 | 27 | tool_fetch() { 28 | curl -L ${ARCHIVE_URL} -o "${TOOL_DOWNLOAD_DIR}/${ARCHIVE_NAME}.zip" 29 | } 30 | 31 | tool_extract() { 32 | unzip "${TOOL_DOWNLOAD_DIR}/${ARCHIVE_NAME}.zip" -d ${TOOL_DOWNLOAD_DIR} 33 | } 34 | 35 | tool_install() { 36 | install "${TOOL_DOWNLOAD_DIR}/${ARCHIVE_NAME}/${TOOL_NAME}-${TOOL_VERSION}-macos/bin/${TOOL_NAME}" "${TOOLS_INSTALL_PATH}" 37 | } 38 | 39 | # ––––––––––––––––––––––––––– Script ––––––––––––––––––––––––––– 40 | 41 | set_bold=$(tput bold) 42 | set_normal=$(tput sgr0) 43 | 44 | log() { 45 | printf "\n$1 ${set_bold}$2${set_normal}\n" 46 | } 47 | 48 | clean_up() { 49 | rm -rf "${TOOL_DOWNLOAD_DIR}" 50 | } 51 | 52 | set -e 53 | trap clean_up err exit SIGTERM SIGINT 54 | 55 | if [ -f "${TOOL_INSTALL_PATH}" ]; then 56 | log "⚠️" " ${TOOL_NAME} already installed" 57 | exit 0 58 | fi 59 | 60 | if [ ! -d "${TOOL_DOWNLOAD_DIR}" ]; then 61 | mkdir -p "${TOOL_DOWNLOAD_DIR}" 62 | fi 63 | 64 | cd "${TEMP_INSTALL_PATH}" 65 | 66 | log "⬇️" " Fetching ${TOOL_NAME}...\n" 67 | 68 | tool_fetch 69 | 70 | log "📦" " Extracting ${TOOL_NAME}...\n" 71 | 72 | tool_extract 73 | 74 | log "♻️" " Installing ${TOOL_NAME}..." 75 | 76 | tool_install 77 | 78 | log "💧" "Performing cleanup..." 79 | clean_up 80 | 81 | if [ -f "${TOOL_INSTALL_PATH}" ]; then 82 | log "✅" "${TOOL_NAME} successfully installed" 83 | exit 0 84 | fi 85 | 86 | log "🚫" "${TOOL_NAME} failed to install" 87 | -------------------------------------------------------------------------------- /Sources/DeclarativeConfiguration/Exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import FunctionalBuilder 2 | @_exported import FunctionalClosures 3 | @_exported import FunctionalConfigurator 4 | @_exported import FunctionalKeyPath 5 | @_exported import FunctionalModification 6 | -------------------------------------------------------------------------------- /Sources/FunctionalBuilder/Builder.swift: -------------------------------------------------------------------------------- 1 | import FunctionalConfigurator 2 | import FunctionalKeyPath 3 | import FunctionalModification 4 | 5 | @dynamicMemberLookup 6 | public struct Builder { 7 | @usableFromInline 8 | internal var _initialValue: () -> Base 9 | 10 | @usableFromInline 11 | internal var _configurator: Configurator 12 | 13 | @inlinable 14 | public var base: Base { _initialValue() } 15 | 16 | @inlinable 17 | public func build() -> Base { _configurator.configured(base) } 18 | 19 | @inlinable 20 | public func apply() where Base: AnyObject { _ = build() } 21 | 22 | // /// Applies modification to a new builder, created with a built object. 23 | // @inlinable 24 | // public func reinforce( 25 | // _ transform: @escaping (inout Base) -> Void 26 | // ) -> Builder { 27 | // Builder(build()).set(transform) 28 | // } 29 | 30 | /// Applies modification to a new builder, created with a built object, also passes leading parameters to transform function. 31 | @inlinable 32 | public func reinforce( 33 | _ args: repeat each Arg, 34 | transform: @escaping (inout Base, repeat each Arg) -> Void 35 | ) -> Builder { 36 | Builder(build()).set { base in transform(&base, repeat each args) } 37 | } 38 | 39 | // /// Applies modification to a new builder, created with a built object, also passes leading parameters to transform function. 40 | // @inlinable 41 | // public func reinforce( 42 | // _ t0: T0, 43 | // t1: T1, 44 | // _ transform: @escaping (inout Base, T0, T1) -> Void 45 | // ) -> Builder { 46 | // reinforce { base in transform(&base, t0, t1) } 47 | // } 48 | // 49 | // /// Applies modification to a new builder, created with a built object, also passes leading parameters to transform function. 50 | // @inlinable 51 | // public func reinforce( 52 | // _ t0: T0, 53 | // _ t1: T1, 54 | // _ t2: T2, 55 | // _ transform: @escaping (inout Base, T0, T1, T2) -> Void 56 | // ) -> Builder { 57 | // reinforce { base in transform(&base, t0, t1, t2) } 58 | // } 59 | 60 | @inlinable 61 | public func combined(with builder: Builder) -> Builder { 62 | Builder( 63 | _initialValue, 64 | _configurator.combined(with: builder._configurator) 65 | ) 66 | } 67 | 68 | @inlinable 69 | public func combined(with configurator: Configurator) -> Builder { 70 | Builder( 71 | _initialValue, 72 | _configurator.combined(with: configurator) 73 | ) 74 | } 75 | 76 | /// Creates a new instance of builder with initial value 77 | @inlinable 78 | public init(_ initialValue: @escaping @autoclosure () -> Base) { 79 | self.init( 80 | initialValue, 81 | Configurator() 82 | ) 83 | } 84 | 85 | @usableFromInline 86 | internal init( 87 | _ initialValue: @escaping () -> Base, 88 | _ configurator: Configurator 89 | ) { 90 | self._initialValue = initialValue 91 | self._configurator = configurator 92 | } 93 | 94 | /// Appends transformation to current configuration 95 | @inlinable 96 | public func set( 97 | _ transform: @escaping (inout Base) -> Void 98 | ) -> Builder { 99 | Builder( 100 | _initialValue, 101 | _configurator.set(transform) 102 | ) 103 | } 104 | 105 | @inlinable 106 | public subscript( 107 | dynamicMember keyPath: WritableKeyPath 108 | ) -> CallableBlock { 109 | CallableBlock( 110 | builder: self, 111 | keyPath: FunctionalKeyPath(keyPath) 112 | ) 113 | } 114 | 115 | @inlinable 116 | public subscript( 117 | dynamicMember keyPath: KeyPath 118 | ) -> NonCallableBlock { 119 | NonCallableBlock( 120 | builder: self, 121 | keyPath: .getonly(keyPath) 122 | ) 123 | } 124 | 125 | @inlinable 126 | public subscript( 127 | dynamicMember keyPath: WritableKeyPath 128 | ) -> CallableBlock where Base == Wrapped? { 129 | CallableBlock( 130 | builder: self, 131 | keyPath: FunctionalKeyPath(keyPath).optional() 132 | ) 133 | } 134 | 135 | @inlinable 136 | public subscript( 137 | dynamicMember keyPath: KeyPath 138 | ) -> NonCallableBlock where Base == Wrapped? { 139 | NonCallableBlock( 140 | builder: self, 141 | keyPath: FunctionalKeyPath.getonly(keyPath).optional() 142 | ) 143 | } 144 | } 145 | 146 | extension Builder { 147 | @dynamicMemberLookup 148 | public struct CallableBlock { 149 | @usableFromInline 150 | internal var _block: NonCallableBlock 151 | 152 | @usableFromInline 153 | internal init( 154 | builder: Builder, 155 | keyPath: FunctionalKeyPath 156 | ) { 157 | self._block = .init( 158 | builder: builder, 159 | keyPath: keyPath 160 | ) 161 | } 162 | 163 | @inlinable 164 | public func callAsFunction( 165 | if condition: Bool, 166 | then thenValue: @escaping @autoclosure () -> Value 167 | ) -> Builder { 168 | Builder( 169 | _block.builder._initialValue, 170 | _block.builder._configurator.appendingConfiguration { base in 171 | if condition { 172 | return _block.keyPath.embed(thenValue(), in: base) 173 | } else { 174 | return base 175 | } 176 | } 177 | ) 178 | } 179 | 180 | @inlinable 181 | public func scope(_ builder: @escaping (Builder) -> Builder) -> Builder { 182 | Builder( 183 | _block.builder._initialValue, 184 | _block.builder._configurator.appendingConfiguration { base in 185 | _block.keyPath.embed( 186 | builder(.init(_block.keyPath.extract(from: base))).build(), 187 | in: base 188 | ) 189 | } 190 | ) 191 | } 192 | 193 | @inlinable 194 | public func ifLetScope( 195 | _ builder: @escaping (Builder) -> Builder 196 | ) -> Builder where Value == Wrapped? { 197 | Builder( 198 | _block.builder._initialValue, 199 | _block.builder._configurator.appendingConfiguration { base in 200 | guard let value = _block.keyPath.extract(from: base) else { return base } 201 | return _block.keyPath.embed( 202 | builder(.init(value)).build(), 203 | in: base 204 | ) 205 | } 206 | ) 207 | } 208 | 209 | @inlinable 210 | public func callAsFunction( 211 | if condition: Bool, 212 | then thenValue: @escaping @autoclosure () -> Value, 213 | else elseValue: (() -> Value)? = nil 214 | ) -> Builder { 215 | Builder( 216 | _block.builder._initialValue, 217 | _block.builder._configurator.appendingConfiguration { base in 218 | if condition { 219 | return _block.keyPath.embed(thenValue(), in: base) 220 | } else if let value = elseValue?() { 221 | return _block.keyPath.embed(value, in: base) 222 | } else { 223 | return base 224 | } 225 | } 226 | ) 227 | } 228 | 229 | @inlinable 230 | public func callAsFunction(_ value: @escaping @autoclosure () -> Value) -> Builder { 231 | Builder( 232 | _block.builder._initialValue, 233 | _block.builder._configurator.appendingConfiguration { base in 234 | _block.keyPath.embed(value(), in: base) 235 | } 236 | ) 237 | } 238 | 239 | @inlinable 240 | public func set(_ transform: @escaping (inout Value) -> Void) -> Builder { 241 | Builder( 242 | _block.builder._initialValue, 243 | _block.builder._configurator.appendingConfiguration { base in 244 | _block.keyPath.embed( 245 | reduce(_block.keyPath.extract(from: base), with: transform), 246 | in: base 247 | ) 248 | } 249 | ) 250 | } 251 | 252 | @inlinable 253 | public subscript( 254 | dynamicMember keyPath: WritableKeyPath 255 | ) -> CallableBlock { 256 | CallableBlock( 257 | builder: _block.builder, 258 | keyPath: _block.keyPath.appending(path: .init(keyPath)) 259 | ) 260 | } 261 | 262 | @inlinable 263 | public subscript( 264 | dynamicMember keyPath: KeyPath 265 | ) -> NonCallableBlock { 266 | _block[dynamicMember: keyPath] 267 | } 268 | 269 | @inlinable 270 | public subscript( 271 | dynamicMember keyPath: WritableKeyPath 272 | ) -> CallableBlock where Value == Wrapped? { 273 | CallableBlock( 274 | builder: _block.builder, 275 | keyPath: _block.keyPath.appending( 276 | path: FunctionalKeyPath(keyPath).optional() 277 | ) 278 | ) 279 | } 280 | 281 | @inlinable 282 | public subscript( 283 | dynamicMember keyPath: KeyPath 284 | ) -> NonCallableBlock where Value == Wrapped? { 285 | NonCallableBlock( 286 | builder: _block.builder, 287 | keyPath: _block.keyPath.appending( 288 | path: FunctionalKeyPath.getonly(keyPath).optional() 289 | ) 290 | ) 291 | } 292 | } 293 | 294 | @dynamicMemberLookup 295 | public struct NonCallableBlock { 296 | @usableFromInline 297 | internal var builder: Builder 298 | 299 | @usableFromInline 300 | internal var keyPath: FunctionalKeyPath 301 | 302 | @usableFromInline 303 | internal init( 304 | builder: Builder, 305 | keyPath: FunctionalKeyPath 306 | ) { 307 | self.builder = builder 308 | self.keyPath = keyPath 309 | } 310 | 311 | @inlinable 312 | public func scope( 313 | _ builder: @escaping (Builder) -> Builder 314 | ) -> Builder where Value: AnyObject { 315 | Builder( 316 | self.builder._initialValue, 317 | self.builder._configurator.appendingConfiguration { base in 318 | keyPath.embed( 319 | builder(.init(keyPath.extract(from: base))).build(), 320 | in: base 321 | ) 322 | } 323 | ) 324 | } 325 | 326 | @inlinable 327 | public func ifLetScope( 328 | _ builder: @escaping (Builder) -> Builder 329 | ) -> Builder where Wrapped: AnyObject, Value == Wrapped? { 330 | Builder( 331 | self.builder._initialValue, 332 | self.builder._configurator.appendingConfiguration { base in 333 | guard let value = keyPath.extract(from: base) else { return base } 334 | return keyPath.embed( 335 | builder(.init(value)).build(), 336 | in: base 337 | ) 338 | } 339 | ) 340 | } 341 | 342 | @inlinable 343 | public subscript( 344 | dynamicMember keyPath: ReferenceWritableKeyPath 345 | ) -> CallableBlock { 346 | CallableBlock( 347 | builder: self.builder, 348 | keyPath: self.keyPath.appending(path: .init(keyPath)) 349 | ) 350 | } 351 | 352 | @inlinable 353 | public subscript( 354 | dynamicMember keyPath: KeyPath 355 | ) -> NonCallableBlock { 356 | NonCallableBlock( 357 | builder: self.builder, 358 | keyPath: self.keyPath.appending(path: .getonly(keyPath)) 359 | ) 360 | } 361 | 362 | @inlinable 363 | public subscript( 364 | dynamicMember keyPath: ReferenceWritableKeyPath 365 | ) -> CallableBlock where Value == Wrapped? { 366 | CallableBlock( 367 | builder: self.builder, 368 | keyPath: self.keyPath.appending( 369 | path: FunctionalKeyPath(keyPath).optional() 370 | ) 371 | ) 372 | } 373 | 374 | @inlinable 375 | public subscript( 376 | dynamicMember keyPath: KeyPath 377 | ) -> NonCallableBlock where Value == Wrapped? { 378 | NonCallableBlock( 379 | builder: self.builder, 380 | keyPath: self.keyPath.appending( 381 | path: FunctionalKeyPath.getonly(keyPath).optional() 382 | ) 383 | ) 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /Sources/FunctionalBuilder/BuilderProvider.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol BuilderProvider {} 4 | extension BuilderProvider { 5 | @inlinable 6 | public var builder: Builder { .init(self) } 7 | } 8 | 9 | extension NSObject: BuilderProvider {} 10 | -------------------------------------------------------------------------------- /Sources/FunctionalClosures/DataSource.swift: -------------------------------------------------------------------------------- 1 | /// A wrapper for clusure-based interaction between objects 2 | /// 3 | /// Provides a public API to set internal closure-based datasource with a functional API 4 | @propertyWrapper 5 | public class DataSource { 6 | public struct Container { 7 | @usableFromInline 8 | internal var action: (Input) -> Output 9 | 10 | public init(action: @escaping (Input) -> Output) { 11 | self.action = action 12 | } 13 | 14 | @inlinable 15 | public mutating func callAsFunction(perform action: @escaping (Input) -> Output) { 16 | self.action = action 17 | } 18 | } 19 | 20 | public init(wrappedValue: Container) { 21 | self.wrappedValue = wrappedValue 22 | } 23 | 24 | public var wrappedValue: Container 25 | 26 | @inlinable 27 | public var projectedValue: (Input) -> Output { 28 | get { wrappedValue.action } 29 | set { wrappedValue.action = newValue } 30 | } 31 | 32 | @inlinable 33 | public func callAsFunction(_ input: Input) -> Output? { 34 | projectedValue(input) 35 | } 36 | 37 | @inlinable 38 | public func callAsFunction() -> Output where Input == Void { 39 | projectedValue(()) 40 | } 41 | } 42 | 43 | /// A wrapper for clusure-based interaction between objects 44 | /// 45 | /// Provides a public API to set internal closure-based datasource with a functional API 46 | @propertyWrapper 47 | public class OptionalDataSource { 48 | public struct Container { 49 | @usableFromInline 50 | internal var action: ((Input) -> Output)? 51 | 52 | internal init() {} 53 | 54 | public init(action: ((Input) -> Output)?) { 55 | self.action = action 56 | } 57 | 58 | @inlinable 59 | public mutating func callAsFunction(perform action: ((Input) -> Output)?) { 60 | self.action = action 61 | } 62 | } 63 | 64 | public init() {} 65 | 66 | public init(wrappedValue: Container) { 67 | self.wrappedValue = wrappedValue 68 | } 69 | 70 | public var wrappedValue: Container = .init() 71 | 72 | @inlinable 73 | public var projectedValue: ((Input) -> Output)? { 74 | get { wrappedValue.action } 75 | set { wrappedValue.action = newValue } 76 | } 77 | 78 | @inlinable 79 | public func callAsFunction(_ input: Input) -> Output? { 80 | projectedValue?(input) 81 | } 82 | 83 | @inlinable 84 | public func callAsFunction() -> Output? where Input == Void { 85 | projectedValue?(()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Sources/FunctionalClosures/Deprecations/Assign.swift: -------------------------------------------------------------------------------- 1 | @available(*, deprecated, message: """ 2 | Will be removed in future versions of swift-declarative-configuration 3 | """) 4 | @inlinable 5 | public func assign( 6 | to root: Root, 7 | _ keyPath: ReferenceWritableKeyPath 8 | ) -> (T0) -> Void { 9 | { [weak root] result in root?[keyPath: keyPath] = result } 10 | } 11 | 12 | @available(*, deprecated, message: """ 13 | Will be removed in future versions of swift-declarative-configuration 14 | """) 15 | @inlinable 16 | public func assignFirst( 17 | to root: Root, 18 | _ keyPath: ReferenceWritableKeyPath 19 | ) -> (T0, T1) -> Void { 20 | { [weak root] result, _ in root?[keyPath: keyPath] = result } 21 | } 22 | 23 | @available(*, deprecated, message: """ 24 | Will be removed in future versions of swift-declarative-configuration 25 | """) 26 | @inlinable 27 | public func assignFirst( 28 | to root: Root, 29 | _ keyPath: ReferenceWritableKeyPath 30 | ) -> (T0, T1, T2) -> Void { 31 | { [weak root] result, _, _ in root?[keyPath: keyPath] = result } 32 | } 33 | 34 | @available(*, deprecated, message: """ 35 | Will be removed in future versions of swift-declarative-configuration 36 | """) 37 | @inlinable 38 | public func assignFirst( 39 | to root: Root, 40 | _ keyPath: ReferenceWritableKeyPath 41 | ) -> (T0, T1, T2, T3) -> Void { 42 | { [weak root] result, _, _, _ in root?[keyPath: keyPath] = result } 43 | } 44 | 45 | @available(*, deprecated, message: """ 46 | Will be removed in future versions of swift-declarative-configuration 47 | """) 48 | @inlinable 49 | public func assignFirst( 50 | to root: Root, 51 | _ keyPath: ReferenceWritableKeyPath 52 | ) -> (T0, T1, T2, T3, T4) -> Void { 53 | { [weak root] result, _, _, _, _ in root?[keyPath: keyPath] = result } 54 | } 55 | 56 | @available(*, deprecated, message: """ 57 | Will be removed in future versions of swift-declarative-configuration 58 | """) 59 | @inlinable 60 | public func assignSecond( 61 | to root: Root, 62 | _ keyPath: ReferenceWritableKeyPath 63 | ) -> (T0, T1) -> Void { 64 | { [weak root] _, result in root?[keyPath: keyPath] = result } 65 | } 66 | 67 | @available(*, deprecated, message: """ 68 | Will be removed in future versions of swift-declarative-configuration 69 | """) 70 | @inlinable 71 | public func assignSecond( 72 | to root: Root, 73 | _ keyPath: ReferenceWritableKeyPath 74 | ) -> (T0, T1, T2) -> Void { 75 | { [weak root] _, result, _ in root?[keyPath: keyPath] = result } 76 | } 77 | 78 | @available(*, deprecated, message: """ 79 | Will be removed in future versions of swift-declarative-configuration 80 | """) 81 | @inlinable 82 | public func assignSecond( 83 | to root: Root, 84 | _ keyPath: ReferenceWritableKeyPath 85 | ) -> (T0, T1, T2, T3) -> Void { 86 | { [weak root] _, result, _, _ in root?[keyPath: keyPath] = result } 87 | } 88 | 89 | @available(*, deprecated, message: """ 90 | Will be removed in future versions of swift-declarative-configuration 91 | """) 92 | @inlinable 93 | public func assignSecond( 94 | to root: Root, 95 | _ keyPath: ReferenceWritableKeyPath 96 | ) -> (T0, T1, T2, T3, T4) -> Void { 97 | { [weak root] _, result, _, _, _ in root?[keyPath: keyPath] = result } 98 | } 99 | 100 | @available(*, deprecated, message: """ 101 | Will be removed in future versions of swift-declarative-configuration 102 | """) 103 | @inlinable 104 | public func assignThird( 105 | to root: Root, 106 | _ keyPath: ReferenceWritableKeyPath 107 | ) -> (T0, T1, T2) -> Void { 108 | { [weak root] _, _, result in root?[keyPath: keyPath] = result } 109 | } 110 | 111 | @available(*, deprecated, message: """ 112 | Will be removed in future versions of swift-declarative-configuration 113 | """) 114 | @inlinable 115 | public func assignThird( 116 | to root: Root, 117 | _ keyPath: ReferenceWritableKeyPath 118 | ) -> (T0, T1, T2, T3) -> Void { 119 | { [weak root] _, _, result, _ in root?[keyPath: keyPath] = result } 120 | } 121 | 122 | @available(*, deprecated, message: """ 123 | Will be removed in future versions of swift-declarative-configuration 124 | """) 125 | @inlinable 126 | public func assignThird( 127 | to root: Root, 128 | _ keyPath: ReferenceWritableKeyPath 129 | ) -> (T0, T1, T2, T3, T4) -> Void { 130 | { [weak root] _, _, result, _, _ in root?[keyPath: keyPath] = result } 131 | } 132 | 133 | @available(*, deprecated, message: """ 134 | Will be removed in future versions of swift-declarative-configuration 135 | """) 136 | @inlinable 137 | public func assignFourth( 138 | to root: Root, 139 | _ keyPath: ReferenceWritableKeyPath 140 | ) -> (T0, T1, T2, T3) -> Void { 141 | { [weak root] _, _, _, result in root?[keyPath: keyPath] = result } 142 | } 143 | 144 | @available(*, deprecated, message: """ 145 | Will be removed in future versions of swift-declarative-configuration 146 | """) 147 | @inlinable 148 | public func assignFourth( 149 | to root: Root, 150 | _ keyPath: ReferenceWritableKeyPath 151 | ) -> (T0, T1, T2, T3, T4) -> Void { 152 | { [weak root] _, _, _, result, _ in root?[keyPath: keyPath] = result } 153 | } 154 | 155 | @available(*, deprecated, message: """ 156 | Will be removed in future versions of swift-declarative-configuration 157 | """) 158 | @inlinable 159 | public func assignFifth( 160 | to root: Root, 161 | _ keyPath: ReferenceWritableKeyPath 162 | ) -> (T0, T1, T2, T3, T4) -> Void { 163 | { [weak root] _, _, _, _, result in root?[keyPath: keyPath] = result } 164 | } 165 | 166 | @available(*, deprecated, message: """ 167 | Will be removed in future versions of swift-declarative-configuration 168 | """) 169 | @inlinable 170 | public func assign( 171 | to root: Root, 172 | _ keyPath: ReferenceWritableKeyPath 173 | ) -> (T0) -> Void { 174 | { [weak root] result in root?[keyPath: keyPath] = result } 175 | } 176 | 177 | @available(*, deprecated, message: """ 178 | Will be removed in future versions of swift-declarative-configuration 179 | """) 180 | @inlinable 181 | public func assignFirst( 182 | to root: Root, 183 | _ keyPath: ReferenceWritableKeyPath 184 | ) -> (T0, T1) -> Void { 185 | { [weak root] result, _ in root?[keyPath: keyPath] = result } 186 | } 187 | 188 | @available(*, deprecated, message: """ 189 | Will be removed in future versions of swift-declarative-configuration 190 | """) 191 | @inlinable 192 | public func assignFirst( 193 | to root: Root, 194 | _ keyPath: ReferenceWritableKeyPath 195 | ) -> (T0, T1, T2) -> Void { 196 | { [weak root] result, _, _ in root?[keyPath: keyPath] = result } 197 | } 198 | 199 | @available(*, deprecated, message: """ 200 | Will be removed in future versions of swift-declarative-configuration 201 | """) 202 | @inlinable 203 | public func assignFirst( 204 | to root: Root, 205 | _ keyPath: ReferenceWritableKeyPath 206 | ) -> (T0, T1, T2, T3) -> Void { 207 | { [weak root] result, _, _, _ in root?[keyPath: keyPath] = result } 208 | } 209 | 210 | @available(*, deprecated, message: """ 211 | Will be removed in future versions of swift-declarative-configuration 212 | """) 213 | @inlinable 214 | public func assignFirst( 215 | to root: Root, 216 | _ keyPath: ReferenceWritableKeyPath 217 | ) -> (T0, T1, T2, T3, T4) -> Void { 218 | { [weak root] result, _, _, _, _ in root?[keyPath: keyPath] = result } 219 | } 220 | 221 | @available(*, deprecated, message: """ 222 | Will be removed in future versions of swift-declarative-configuration 223 | """) 224 | @inlinable 225 | public func assignSecond( 226 | to root: Root, 227 | _ keyPath: ReferenceWritableKeyPath 228 | ) -> (T0, T1) -> Void { 229 | { [weak root] _, result in root?[keyPath: keyPath] = result } 230 | } 231 | 232 | @available(*, deprecated, message: """ 233 | Will be removed in future versions of swift-declarative-configuration 234 | """) 235 | @inlinable 236 | public func assignSecond( 237 | to root: Root, 238 | _ keyPath: ReferenceWritableKeyPath 239 | ) -> (T0, T1, T2) -> Void { 240 | { [weak root] _, result, _ in root?[keyPath: keyPath] = result } 241 | } 242 | 243 | @available(*, deprecated, message: """ 244 | Will be removed in future versions of swift-declarative-configuration 245 | """) 246 | @inlinable 247 | public func assignSecond( 248 | to root: Root, 249 | _ keyPath: ReferenceWritableKeyPath 250 | ) -> (T0, T1, T2, T3) -> Void { 251 | { [weak root] _, result, _, _ in root?[keyPath: keyPath] = result } 252 | } 253 | 254 | @available(*, deprecated, message: """ 255 | Will be removed in future versions of swift-declarative-configuration 256 | """) 257 | @inlinable 258 | public func assignSecond( 259 | to root: Root, 260 | _ keyPath: ReferenceWritableKeyPath 261 | ) -> (T0, T1, T2, T3, T4) -> Void { 262 | { [weak root] _, result, _, _, _ in root?[keyPath: keyPath] = result } 263 | } 264 | 265 | @available(*, deprecated, message: """ 266 | Will be removed in future versions of swift-declarative-configuration 267 | """) 268 | @inlinable 269 | public func assignThird( 270 | to root: Root, 271 | _ keyPath: ReferenceWritableKeyPath 272 | ) -> (T0, T1, T2) -> Void { 273 | { [weak root] _, _, result in root?[keyPath: keyPath] = result } 274 | } 275 | 276 | @available(*, deprecated, message: """ 277 | Will be removed in future versions of swift-declarative-configuration 278 | """) 279 | @inlinable 280 | public func assignThird( 281 | to root: Root, 282 | _ keyPath: ReferenceWritableKeyPath 283 | ) -> (T0, T1, T2, T3) -> Void { 284 | { [weak root] _, _, result, _ in root?[keyPath: keyPath] = result } 285 | } 286 | 287 | @available(*, deprecated, message: """ 288 | Will be removed in future versions of swift-declarative-configuration 289 | """) 290 | @inlinable 291 | public func assignThird( 292 | to root: Root, 293 | _ keyPath: ReferenceWritableKeyPath 294 | ) -> (T0, T1, T2, T3, T4) -> Void { 295 | { [weak root] _, _, result, _, _ in root?[keyPath: keyPath] = result } 296 | } 297 | 298 | @available(*, deprecated, message: """ 299 | Will be removed in future versions of swift-declarative-configuration 300 | """) 301 | @inlinable 302 | public func assignFourth( 303 | to root: Root, 304 | _ keyPath: ReferenceWritableKeyPath 305 | ) -> (T0, T1, T2, T3) -> Void { 306 | { [weak root] _, _, _, result in root?[keyPath: keyPath] = result } 307 | } 308 | 309 | @available(*, deprecated, message: """ 310 | Will be removed in future versions of swift-declarative-configuration 311 | """) 312 | @inlinable 313 | public func assignFourth( 314 | to root: Root, 315 | _ keyPath: ReferenceWritableKeyPath 316 | ) -> (T0, T1, T2, T3, T4) -> Void { 317 | { [weak root] _, _, _, result, _ in root?[keyPath: keyPath] = result } 318 | } 319 | 320 | @available(*, deprecated, message: """ 321 | Will be removed in future versions of swift-declarative-configuration, \ 322 | probably will move to `capturecontext/swift-prelude` when 323 | """) 324 | @inlinable 325 | public func assignFifth( 326 | to root: Root, 327 | _ keyPath: ReferenceWritableKeyPath 328 | ) -> (T0, T1, T2, T3, T4) -> Void { 329 | { [weak root] _, _, _, _, result in root?[keyPath: keyPath] = result } 330 | } 331 | -------------------------------------------------------------------------------- /Sources/FunctionalClosures/Handler.swift: -------------------------------------------------------------------------------- 1 | public enum HandlerContainerBehaviour { 2 | case resetting 3 | case preceding 4 | case appending 5 | } 6 | 7 | public typealias Handler = Handler1 8 | 9 | /// A wrapper for clusure-based interaction between objects 10 | /// 11 | /// Provides a public API to set internal closure-based hanlder or delegate with a functional API 12 | @propertyWrapper 13 | public class Handler1 { 14 | public struct Container { 15 | public typealias Behaviour = HandlerContainerBehaviour 16 | 17 | @usableFromInline 18 | internal var action: ((Input) -> Void)? 19 | 20 | internal init() {} 21 | 22 | public init(action: ((Input) -> Void)?) { 23 | self.action = action 24 | } 25 | 26 | @inlinable 27 | public mutating func callAsFunction(perform action: ((Input) -> Void)?) { 28 | self.action = action 29 | } 30 | 31 | @available( 32 | *, 33 | deprecated, 34 | message: 35 | """ 36 | This API will be removed, \ 37 | consider using redeclaration with `(Input) -> Output` signature function. \ 38 | Feel free to discuss the API here \ 39 | https://github.com/CaptureContext/swift-declarative-configuration/issues/1 40 | """ 41 | ) 42 | @inlinable 43 | public mutating func callAsFunction(_ behaviour: Behaviour, perform action: ((Input) -> Void)?) 44 | { 45 | switch behaviour { 46 | case .resetting: 47 | self.action = action 48 | case .preceding: 49 | let oldAction = self.action 50 | self.action = { input in 51 | action?(input) 52 | oldAction?(input) 53 | } 54 | case .appending: 55 | let oldAction = self.action 56 | self.action = { input in 57 | action?(input) 58 | oldAction?(input) 59 | } 60 | } 61 | } 62 | } 63 | 64 | public init() {} 65 | 66 | public init(wrappedValue: Container) { 67 | self.wrappedValue = wrappedValue 68 | } 69 | 70 | public var wrappedValue: Container = .init() 71 | 72 | public var projectedValue: ((Input) -> Void)? { 73 | get { wrappedValue.action } 74 | set { wrappedValue.action = newValue } 75 | } 76 | 77 | public func callAsFunction(_ input: Input) { 78 | projectedValue?(input) 79 | } 80 | 81 | public func callAsFunction() where Input == Void { 82 | projectedValue?(()) 83 | } 84 | } 85 | 86 | // MARK: Typed handlers 87 | 88 | /// A wrapper for clusure-based interaction between objects 89 | /// 90 | /// Provides a public API to set internal closure-based hanlder or delegate with a functional API 91 | @propertyWrapper 92 | public class Handler2 { 93 | public struct Container { 94 | public typealias Behaviour = HandlerContainerBehaviour 95 | 96 | internal var action: ((T0, T1) -> Void)? 97 | 98 | internal init() {} 99 | 100 | public init(action: ((T0, T1) -> Void)?) { 101 | self.action = action 102 | } 103 | 104 | public mutating func callAsFunction(perform action: ((T0, T1) -> Void)?) { 105 | self.action = action 106 | } 107 | 108 | @available( 109 | *, 110 | deprecated, 111 | message: 112 | """ 113 | This API will be removed, \ 114 | consider using redeclaration with `(Input) -> Output` signature function. \ 115 | Feel free to discuss the API here \ 116 | https://github.com/MakeupStudio/swift-declarative-configuration/issues/1 117 | """ 118 | ) 119 | public mutating func callAsFunction(_ behaviour: Behaviour, perform action: ((T0, T1) -> Void)?) 120 | { 121 | switch behaviour { 122 | case .resetting: 123 | self.action = action 124 | case .preceding: 125 | let oldAction = self.action 126 | self.action = { t0, t1 in 127 | action?(t0, t1) 128 | oldAction?(t0, t1) 129 | } 130 | case .appending: 131 | let oldAction = self.action 132 | self.action = { t0, t1 in 133 | action?(t0, t1) 134 | oldAction?(t0, t1) 135 | } 136 | } 137 | } 138 | } 139 | 140 | public init() {} 141 | 142 | public init(wrappedValue: Container) { 143 | self.wrappedValue = wrappedValue 144 | } 145 | 146 | public var wrappedValue: Container = .init() 147 | 148 | public var projectedValue: ((T0, T1) -> Void)? { 149 | get { wrappedValue.action } 150 | set { wrappedValue.action = newValue } 151 | } 152 | 153 | public func callAsFunction(_ t0: T0, _ t1: T1) { 154 | projectedValue?(t0, t1) 155 | } 156 | } 157 | 158 | /// A wrapper for clusure-based interaction between objects 159 | /// 160 | /// Provides a public API to set internal closure-based hanlder or delegate with a functional API 161 | @propertyWrapper 162 | public class Handler3 { 163 | public struct Container { 164 | public typealias Behaviour = HandlerContainerBehaviour 165 | 166 | internal var action: ((T0, T1, T2) -> Void)? 167 | 168 | internal init() {} 169 | 170 | public init(action: ((T0, T1, T2) -> Void)?) { 171 | self.action = action 172 | } 173 | 174 | public mutating func callAsFunction(perform action: ((T0, T1, T2) -> Void)?) { 175 | self.action = action 176 | } 177 | 178 | @available( 179 | *, 180 | deprecated, 181 | message: 182 | """ 183 | This API will be removed, \ 184 | consider using redeclaration with `(Input) -> Output` signature function. \ 185 | Feel free to discuss the API here \ 186 | https://github.com/MakeupStudio/swift-declarative-configuration/issues/1 187 | """ 188 | ) 189 | public mutating func callAsFunction( 190 | _ behaviour: Behaviour, 191 | perform action: ((T0, T1, T2) -> Void)? 192 | ) { 193 | switch behaviour { 194 | case .resetting: 195 | self.action = action 196 | case .preceding: 197 | let oldAction = self.action 198 | self.action = { t0, t1, t2 in 199 | action?(t0, t1, t2) 200 | oldAction?(t0, t1, t2) 201 | } 202 | case .appending: 203 | let oldAction = self.action 204 | self.action = { t0, t1, t2 in 205 | action?(t0, t1, t2) 206 | oldAction?(t0, t1, t2) 207 | } 208 | } 209 | } 210 | } 211 | 212 | public init() {} 213 | 214 | public init(wrappedValue: Container) { 215 | self.wrappedValue = wrappedValue 216 | } 217 | 218 | public var wrappedValue: Container = .init() 219 | 220 | public var projectedValue: ((T0, T1, T2) -> Void)? { 221 | get { wrappedValue.action } 222 | set { wrappedValue.action = newValue } 223 | } 224 | 225 | public func callAsFunction(_ t0: T0, _ t1: T1, _ t2: T2) { 226 | projectedValue?(t0, t1, t2) 227 | } 228 | } 229 | 230 | /// A wrapper for clusure-based interaction between objects 231 | /// 232 | /// Provides a public API to set internal closure-based hanlder or delegate with a functional API 233 | @propertyWrapper 234 | public class Handler4 { 235 | public struct Container { 236 | public typealias Behaviour = HandlerContainerBehaviour 237 | 238 | internal var action: ((T0, T1, T2, T3) -> Void)? 239 | 240 | internal init() {} 241 | 242 | public init(action: ((T0, T1, T2, T3) -> Void)?) { 243 | self.action = action 244 | } 245 | 246 | public mutating func callAsFunction(perform action: ((T0, T1, T2, T3) -> Void)?) { 247 | self.action = action 248 | } 249 | 250 | @available( 251 | *, 252 | deprecated, 253 | message: 254 | """ 255 | This API will be removed, \ 256 | consider using redeclaration with `(Input) -> Output` signature function. \ 257 | Feel free to discuss the API here \ 258 | https://github.com/MakeupStudio/swift-declarative-configuration/issues/1 259 | """ 260 | ) 261 | public mutating func callAsFunction( 262 | _ behaviour: Behaviour, 263 | perform action: ((T0, T1, T2, T3) -> Void)? 264 | ) { 265 | switch behaviour { 266 | case .resetting: 267 | self.action = action 268 | case .preceding: 269 | let oldAction = self.action 270 | self.action = { t0, t1, t2, t3 in 271 | action?(t0, t1, t2, t3) 272 | oldAction?(t0, t1, t2, t3) 273 | } 274 | case .appending: 275 | let oldAction = self.action 276 | self.action = { t0, t1, t2, t3 in 277 | action?(t0, t1, t2, t3) 278 | oldAction?(t0, t1, t2, t3) 279 | } 280 | } 281 | } 282 | } 283 | 284 | public init() {} 285 | 286 | public init(wrappedValue: Container) { 287 | self.wrappedValue = wrappedValue 288 | } 289 | 290 | public var wrappedValue: Container = .init() 291 | 292 | public var projectedValue: ((T0, T1, T2, T3) -> Void)? { 293 | get { wrappedValue.action } 294 | set { wrappedValue.action = newValue } 295 | } 296 | 297 | public func callAsFunction(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3) { 298 | projectedValue?(t0, t1, t2, t3) 299 | } 300 | } 301 | 302 | /// A wrapper for clusure-based interaction between objects 303 | /// 304 | /// Provides a public API to set internal closure-based hanlder or delegate with a functional API 305 | @propertyWrapper 306 | public class Handler5 { 307 | public struct Container { 308 | public typealias Behaviour = HandlerContainerBehaviour 309 | 310 | internal var action: ((T0, T1, T2, T3, T4) -> Void)? 311 | 312 | internal init() {} 313 | 314 | public init(action: ((T0, T1, T2, T3, T4) -> Void)?) { 315 | self.action = action 316 | } 317 | 318 | public mutating func callAsFunction(perform action: ((T0, T1, T2, T3, T4) -> Void)?) { 319 | self.action = action 320 | } 321 | 322 | @available( 323 | *, 324 | deprecated, 325 | message: 326 | """ 327 | This API will be removed, \ 328 | consider using redeclaration with `(Input) -> Output` signature function. \ 329 | Feel free to discuss the API here \ 330 | https://github.com/MakeupStudio/swift-declarative-configuration/issues/1 331 | """ 332 | ) 333 | public mutating func callAsFunction( 334 | _ behaviour: Behaviour, 335 | perform action: ((T0, T1, T2, T3, T4) -> Void)? 336 | ) { 337 | switch behaviour { 338 | case .resetting: 339 | self.action = action 340 | case .preceding: 341 | let oldAction = self.action 342 | self.action = { t0, t1, t2, t3, t4 in 343 | action?(t0, t1, t2, t3, t4) 344 | oldAction?(t0, t1, t2, t3, t4) 345 | } 346 | case .appending: 347 | let oldAction = self.action 348 | self.action = { t0, t1, t2, t3, t4 in 349 | action?(t0, t1, t2, t3, t4) 350 | oldAction?(t0, t1, t2, t3, t4) 351 | } 352 | } 353 | } 354 | } 355 | 356 | public init() {} 357 | 358 | public init(wrappedValue: Container) { 359 | self.wrappedValue = wrappedValue 360 | } 361 | 362 | public var wrappedValue: Container = .init() 363 | 364 | public var projectedValue: ((T0, T1, T2, T3, T4) -> Void)? { 365 | get { wrappedValue.action } 366 | set { wrappedValue.action = newValue } 367 | } 368 | 369 | public func callAsFunction(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4) { 370 | projectedValue?(t0, t1, t2, t3, t4) 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /Sources/FunctionalConfigurator/ConfigIntializable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Allows to intialize a new object without parameters or with configuration 4 | public protocol ConfigInitializable { 5 | init() 6 | } 7 | 8 | extension ConfigInitializable { 9 | public typealias Config = Configurator 10 | 11 | /// Instantiates a new object with specified configuration 12 | /// 13 | /// Note: Type must implement custom intializer with no parameters, even if it inherits from NSObject 14 | @inlinable 15 | public init(config configuration: (Config) -> Config) { 16 | self.init(config: configuration(Config())) 17 | } 18 | 19 | /// Instantiates a new object with specified configuration 20 | /// 21 | /// Note: Type must implement custom intializer with no parameters, even if it inherits from NSObject 22 | public init(config configurator: Config) { 23 | self = configurator.configured(.init()) 24 | } 25 | } 26 | 27 | public protocol ConfigInitializableNSObject: NSObjectProtocol {} 28 | extension ConfigInitializableNSObject where Self: NSObject { 29 | public typealias Config = Configurator 30 | 31 | /// Instantiates a new object with specified configuration 32 | /// 33 | /// Note: Type must implement custom intializer with no parameters, even if it inherits from NSObject 34 | @inlinable 35 | public init(config configuration: (Config) -> Config) { 36 | self.init(config: configuration(Config())) 37 | } 38 | 39 | /// Instantiates a new object with specified configuration 40 | /// 41 | /// Note: Type must implement custom intializer with no parameters, even if it inherits from NSObject 42 | @inlinable 43 | public init(config configurator: Config) { 44 | self.init() 45 | configurator.configure(self) 46 | } 47 | } 48 | 49 | extension NSObject: ConfigInitializableNSObject {} 50 | -------------------------------------------------------------------------------- /Sources/FunctionalConfigurator/Configurator.swift: -------------------------------------------------------------------------------- 1 | import FunctionalKeyPath 2 | import FunctionalModification 3 | 4 | @dynamicMemberLookup 5 | public struct Configurator { 6 | @usableFromInline 7 | internal var _configure: (Base) -> Base 8 | 9 | /// Creates a new instance of configurator 10 | /// 11 | /// Newly created configurator has no modification set up. 12 | /// So it's `configure` function does not modify input 13 | public init() { self._configure = { $0 } } 14 | 15 | /// Creates a configurator with a configuration function 16 | /// 17 | /// Initial value passed to configuration function is an empty configurator 18 | @inlinable 19 | public init(config configuration: (Configurator) -> Configurator) { 20 | self = configuration(.init()) 21 | } 22 | 23 | /// Modifies an object with specified configuration 24 | @inlinable 25 | public func configure(_ base: inout Base) { 26 | _ = _configure(base) 27 | } 28 | 29 | /// Modifies a reference-type object with specified configuration 30 | @inlinable 31 | public func configure(_ base: Base) where Base: AnyObject { 32 | _ = _configure(base) 33 | } 34 | 35 | /// Modifies returns modified object 36 | /// 37 | /// Note: for reference types it is usually the same object 38 | @inlinable 39 | public func configured(_ base: Base) -> Base { 40 | _configure(base) 41 | } 42 | 43 | /// Appends modification of stored object to stored configuration 44 | @inlinable 45 | public func set(_ transform: @escaping (inout Base) -> Void) -> Configurator { 46 | appendingConfiguration { base in 47 | reduce(_configure(base), with: transform) 48 | } 49 | } 50 | 51 | @inlinable 52 | public func combined(with configurator: Configurator) -> Configurator { 53 | appendingConfiguration(configurator._configure) 54 | } 55 | 56 | /// Appends modification of a new configurator to stored configuration 57 | @available(*, deprecated, message: "Use `combined(with:) instead`") 58 | @inlinable 59 | public func appending(_ configurator: Configurator) -> Configurator { 60 | appendingConfiguration(configurator._configure) 61 | } 62 | 63 | /// Appends configuration to stored configuration 64 | @inlinable 65 | public func appendingConfiguration(_ configuration: @escaping (Base) -> Base) -> Configurator { 66 | reduce(self) { _self in 67 | _self._configure = { configuration(_configure($0)) } 68 | } 69 | } 70 | 71 | @inlinable 72 | public subscript( 73 | dynamicMember keyPath: WritableKeyPath 74 | ) -> CallableBlock { 75 | CallableBlock( 76 | configurator: self, 77 | keyPath: .init(keyPath) 78 | ) 79 | } 80 | 81 | @inlinable 82 | public subscript( 83 | dynamicMember keyPath: KeyPath 84 | ) -> NonCallableBlock { 85 | NonCallableBlock( 86 | configurator: self, 87 | keyPath: .getonly(keyPath) 88 | ) 89 | } 90 | 91 | @inlinable 92 | public subscript( 93 | dynamicMember keyPath: WritableKeyPath 94 | ) -> CallableBlock where Base == Wrapped? { 95 | CallableBlock( 96 | configurator: self, 97 | keyPath: FunctionalKeyPath(keyPath).optional() 98 | ) 99 | } 100 | 101 | @inlinable 102 | public subscript( 103 | dynamicMember keyPath: KeyPath 104 | ) -> NonCallableBlock where Base == Wrapped? { 105 | NonCallableBlock( 106 | configurator: self, 107 | keyPath: FunctionalKeyPath.getonly(keyPath).optional() 108 | ) 109 | } 110 | 111 | @inlinable 112 | public static subscript( 113 | dynamicMember keyPath: WritableKeyPath 114 | ) -> CallableBlock { 115 | Configurator()[dynamicMember: keyPath] 116 | } 117 | 118 | @inlinable 119 | public static subscript( 120 | dynamicMember keyPath: KeyPath 121 | ) -> NonCallableBlock { 122 | Configurator()[dynamicMember: keyPath] 123 | } 124 | 125 | @inlinable 126 | public static subscript( 127 | dynamicMember keyPath: WritableKeyPath 128 | ) -> CallableBlock where Base == Wrapped? { 129 | Configurator()[dynamicMember: keyPath] 130 | } 131 | 132 | @inlinable 133 | public static subscript( 134 | dynamicMember keyPath: KeyPath 135 | ) -> NonCallableBlock where Base == Wrapped? { 136 | Configurator()[dynamicMember: keyPath] 137 | } 138 | 139 | @inlinable 140 | public static func set(_ transform: @escaping (inout Base) -> Void) -> Configurator { 141 | Configurator().set(transform) 142 | } 143 | } 144 | 145 | extension Configurator { 146 | @dynamicMemberLookup 147 | public struct CallableBlock { 148 | @usableFromInline 149 | internal var _block: NonCallableBlock 150 | 151 | @usableFromInline 152 | internal init( 153 | configurator: Configurator, 154 | keyPath: FunctionalKeyPath 155 | ) { 156 | self._block = .init( 157 | configurator: configurator, 158 | keyPath: keyPath 159 | ) 160 | } 161 | 162 | @inlinable 163 | public func callAsFunction(_ value: Value) -> Configurator { 164 | _block.configurator.appendingConfiguration { 165 | _block.keyPath.embed(value, in: $0) 166 | } 167 | } 168 | 169 | @inlinable 170 | public func set(_ transform: @escaping (inout Value) -> Void) -> Configurator { 171 | _block.configurator.appendingConfiguration { base in 172 | _block.keyPath.embed( 173 | reduce( 174 | _block.keyPath.extract(from: base), 175 | with: transform 176 | ), 177 | in: base 178 | ) 179 | } 180 | } 181 | 182 | @inlinable 183 | public func scope( 184 | _ configuration: @escaping (Configurator) -> Configurator 185 | ) -> Configurator { 186 | _block.configurator.appendingConfiguration { base in 187 | _block.keyPath.embed( 188 | reduce( 189 | _block.keyPath.extract(from: base), 190 | with: configuration 191 | ), 192 | in: base 193 | ) 194 | } 195 | } 196 | 197 | @inlinable 198 | public func ifLetScope( 199 | _ configuration: @escaping (Configurator) -> Configurator 200 | ) -> Configurator where Value == Wrapped? { 201 | _block.configurator.appendingConfiguration { base in 202 | guard let value = _block.keyPath.extract(from: base) 203 | else { return base } 204 | 205 | return _block.keyPath.embed( 206 | reduce(value, with: configuration), 207 | in: base 208 | ) 209 | } 210 | } 211 | 212 | @inlinable 213 | public subscript( 214 | dynamicMember keyPath: WritableKeyPath 215 | ) -> CallableBlock { 216 | CallableBlock( 217 | configurator: _block.configurator, 218 | keyPath: _block.keyPath 219 | .appending(path: FunctionalKeyPath(keyPath)) 220 | ) 221 | } 222 | 223 | @inlinable 224 | public subscript( 225 | dynamicMember keyPath: KeyPath 226 | ) -> NonCallableBlock { 227 | _block[dynamicMember: keyPath] 228 | } 229 | 230 | @inlinable 231 | public subscript( 232 | dynamicMember keyPath: WritableKeyPath 233 | ) -> CallableBlock where Value == Wrapped? { 234 | CallableBlock( 235 | configurator: _block.configurator, 236 | keyPath: _block.keyPath.appending( 237 | path: FunctionalKeyPath(keyPath).optional() 238 | ) 239 | ) 240 | } 241 | 242 | @inlinable 243 | public subscript( 244 | dynamicMember keyPath: KeyPath 245 | ) -> NonCallableBlock where Value == Wrapped? { 246 | _block[dynamicMember: keyPath] 247 | } 248 | } 249 | 250 | @dynamicMemberLookup 251 | public struct NonCallableBlock { 252 | @usableFromInline 253 | internal var configurator: Configurator 254 | 255 | @usableFromInline 256 | internal var keyPath: FunctionalKeyPath 257 | 258 | @usableFromInline 259 | internal init( 260 | configurator: Configurator, 261 | keyPath: FunctionalKeyPath 262 | ) { 263 | self.configurator = configurator 264 | self.keyPath = keyPath 265 | } 266 | 267 | @inlinable 268 | public func scope( 269 | _ configuration: @escaping (Configurator) -> Configurator 270 | ) -> Configurator where Value: AnyObject { 271 | configurator.appendingConfiguration { base in 272 | keyPath.embed( 273 | reduce( 274 | keyPath.extract(from: base), 275 | with: configuration 276 | ), 277 | in: base 278 | ) 279 | } 280 | } 281 | 282 | @inlinable 283 | public func ifLetScope( 284 | _ configuration: @escaping (Configurator) -> Configurator 285 | ) -> Configurator where Wrapped: AnyObject, Value == Wrapped? { 286 | configurator.appendingConfiguration { base in 287 | guard let value = keyPath.extract(from: base) 288 | else { return base } 289 | 290 | return keyPath.embed( 291 | reduce(value, with: configuration), 292 | in: base 293 | ) 294 | } 295 | } 296 | 297 | @inlinable 298 | public subscript( 299 | dynamicMember keyPath: ReferenceWritableKeyPath 300 | ) -> CallableBlock { 301 | .init( 302 | configurator: self.configurator, 303 | keyPath: self.keyPath.appending(path: FunctionalKeyPath(keyPath)) 304 | ) 305 | } 306 | 307 | @inlinable 308 | public subscript( 309 | dynamicMember keyPath: KeyPath 310 | ) -> NonCallableBlock { 311 | .init( 312 | configurator: self.configurator, 313 | keyPath: self.keyPath.appending(path: .getonly(keyPath)) 314 | ) 315 | } 316 | 317 | @inlinable 318 | public subscript( 319 | dynamicMember keyPath: ReferenceWritableKeyPath 320 | ) -> CallableBlock where Value == Wrapped? { 321 | CallableBlock( 322 | configurator: self.configurator, 323 | keyPath: self.keyPath.appending(path: FunctionalKeyPath(keyPath)) 324 | ) 325 | } 326 | 327 | @inlinable 328 | public subscript( 329 | dynamicMember keyPath: KeyPath 330 | ) -> NonCallableBlock where Value == Wrapped? { 331 | NonCallableBlock( 332 | configurator: self.configurator, 333 | keyPath: self.keyPath.appending(path: .getonly(keyPath)) 334 | ) 335 | } 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /Sources/FunctionalConfigurator/CustomConfigurable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol CustomConfigurable {} 4 | 5 | extension CustomConfigurable { 6 | public typealias Config = Configurator 7 | 8 | @inlinable 9 | public func configured(using configuration: (Config) -> Config) -> Self { 10 | configured(using: configuration(Config())) 11 | } 12 | 13 | @inlinable 14 | public func configured(using configurator: Config) -> Self { 15 | configurator.configured(self) 16 | } 17 | } 18 | 19 | extension CustomConfigurable where Self: AnyObject { 20 | @inlinable 21 | public func configure(using configuration: (Config) -> Config) { 22 | configure(using: configuration(Config())) 23 | } 24 | 25 | @inlinable 26 | public func configure(using configurator: Config) { 27 | configurator.configure(self) 28 | } 29 | } 30 | 31 | extension NSObject: CustomConfigurable {} 32 | -------------------------------------------------------------------------------- /Sources/FunctionalConfigurator/Modification.swift: -------------------------------------------------------------------------------- 1 | /// Modifies an object. 2 | /// 3 | /// Returns a new instance for value types 4 | /// Returns modified reference for reference types 5 | @available( 6 | *, 7 | deprecated, 8 | message: 9 | """ 10 | This function will be made internal in `1.0.0` release, implement `CustomConfigurable` protocol for your object and use instance method instead 11 | """ 12 | ) 13 | @inlinable 14 | public func modification( 15 | of object: Object, 16 | with configuration: (Configurator) -> Configurator 17 | ) -> Object { 18 | return Configurator(config: configuration) 19 | .configured(object) 20 | } 21 | 22 | @inlinable 23 | internal func reduce( 24 | _ object: Object, 25 | with configuration: (Configurator) -> Configurator 26 | ) -> Object { 27 | return Configurator(config: configuration).configured(object) 28 | } 29 | -------------------------------------------------------------------------------- /Sources/FunctionalKeyPath/FunctionalKeyPath.swift: -------------------------------------------------------------------------------- 1 | import FunctionalModification 2 | 3 | /// A path that supports embedding a value in a root and attempting to extract a root's embedded 4 | /// value. 5 | /// 6 | /// This type defines key path-like semantics for enum cases. 7 | public struct FunctionalKeyPath { 8 | @usableFromInline 9 | internal let _embed: (Value, Root) -> Root 10 | 11 | @usableFromInline 12 | internal let _extract: (Root) -> Value 13 | 14 | /// Creates a functional keyPath with a pair of functions. 15 | /// 16 | /// - Parameters: 17 | /// - embed: A function that always succeeds in embedding a value in a root. 18 | /// - extract: A function that can optionally fail in extracting a value from a root. 19 | public init( 20 | embed: @escaping (Value, Root) -> Root, 21 | extract: @escaping (Root) -> Value 22 | ) { 23 | self._embed = embed 24 | self._extract = extract 25 | } 26 | 27 | /// Creates a functional keyPath with a writableKeyPath 28 | @inlinable 29 | public init(_ keyPath: WritableKeyPath) { 30 | self.init( 31 | embed: { value, root in 32 | var root = root 33 | root[keyPath: keyPath] = value 34 | return root 35 | }, 36 | extract: { root in 37 | root[keyPath: keyPath] 38 | } 39 | ) 40 | } 41 | 42 | /// Creates a functional keyPath with a writableKeyPath 43 | @inlinable 44 | public static func optional( 45 | _ keyPath: WritableKeyPath 46 | ) -> FunctionalKeyPath { 47 | FunctionalKeyPath(keyPath).optional() 48 | } 49 | 50 | /// Creates a functional keyPath with a keyPath 51 | /// 52 | /// Ignores embed function call 53 | @inlinable 54 | public static func getonly(_ keyPath: KeyPath) -> FunctionalKeyPath { 55 | FunctionalKeyPath( 56 | embed: { _, root in 57 | root 58 | }, 59 | extract: { root in 60 | root[keyPath: keyPath] 61 | } 62 | ) 63 | } 64 | 65 | /// Makes path optional 66 | @inlinable 67 | public func optional() -> FunctionalKeyPath { 68 | FunctionalKeyPath( 69 | embed: { value, root in 70 | guard let root = root, let value = value else { return nil } 71 | return self._embed(value, root) 72 | }, 73 | extract: { root in 74 | guard let root = root else { return nil } 75 | return self._extract(root) 76 | } 77 | ) 78 | } 79 | 80 | /// Returns a root by embedding a value. 81 | /// 82 | /// Note: Value will not be embed if FunctionalKeyPath was initialized by default (non-writable) `KeyPath` via `getonly` function 83 | /// 84 | /// - Parameter value: A value to embed. 85 | /// - Returns: A root that embeds `value`. 86 | @inlinable 87 | public func embed(_ value: Value, in root: Root) -> Root { 88 | _embed(value, root) 89 | } 90 | 91 | /// Returns a root by embedding a value. 92 | /// 93 | /// Note: Value will not be embed if FunctionalKeyPath was initialized by default (non-writable) `KeyPath` via `getonly` function 94 | /// 95 | /// - Parameter value: A value to embed. 96 | @inlinable 97 | public func embed(_ value: Value, in root: inout Root) { 98 | root = embed(value, in: root) 99 | } 100 | 101 | /// Attempts to extract a value from a root. 102 | /// 103 | /// - Parameter root: A root to extract from. 104 | /// - Returns: A value iff it can be extracted from the given root, otherwise `nil`. 105 | @inlinable 106 | public func extract(from root: Root) -> Value { 107 | _extract(root) 108 | } 109 | 110 | /// Returns a new functional keyPath created by appending the given functional keyPath to this one. 111 | /// 112 | /// Use this method to extend this functional keyPath to the value type of another functional keyPath. 113 | /// 114 | /// - Parameter path: The functional keyPath to append. 115 | /// - Returns: A functional keyPath from the root of this functional keyPath to the value type of `path`. 116 | @inlinable 117 | public func appending(path: FunctionalKeyPath) 118 | -> FunctionalKeyPath 119 | { 120 | FunctionalKeyPath( 121 | embed: { appendedValue, root in 122 | self.embed( 123 | path.embed( 124 | appendedValue, 125 | in: self.extract(from: root) 126 | ), 127 | in: root 128 | ) 129 | }, 130 | extract: { root in 131 | path.extract( 132 | from: self.extract(from: root) 133 | ) 134 | } 135 | ) 136 | } 137 | 138 | /// Returns a new functional keyPath created by appending the given functional keyPath to this one. 139 | /// 140 | /// Use this method to extend this functional keyPath to the value type of another functional keyPath. 141 | /// 142 | /// - Parameter path: The functional keyPath to append. 143 | /// - Returns: A functional keyPath from the root of this functional keyPath to the value type of `path`. 144 | @inlinable 145 | public func appending( 146 | path: FunctionalKeyPath 147 | ) -> FunctionalKeyPath where Value == Wrapped? { 148 | appending(path: path.optional()) 149 | } 150 | } 151 | 152 | extension FunctionalKeyPath { 153 | @inlinable 154 | public static func key( 155 | _ key: Key 156 | ) -> FunctionalKeyPath 157 | where Root == [Key: _Value], Value == _Value? 158 | { 159 | FunctionalKeyPath( 160 | embed: { value, root in 161 | reduce(root) { $0[key] = value } 162 | }, 163 | extract: { $0[key] } 164 | ) 165 | } 166 | 167 | @inlinable 168 | public static func index(_ index: Root.Index) -> FunctionalKeyPath 169 | where Root: MutableCollection, Value == Root.Element 170 | { 171 | FunctionalKeyPath( 172 | embed: { value, root in 173 | reduce(root) { root in 174 | root[index] = value 175 | } 176 | }, 177 | extract: { root in 178 | root[index] 179 | } 180 | ) 181 | } 182 | 183 | @inlinable 184 | public static func getonlyIndex(_ index: Root.Index) -> FunctionalKeyPath 185 | where Root: Collection, Value == Root.Element 186 | { 187 | FunctionalKeyPath( 188 | embed: { _, root in root }, 189 | extract: { $0[index] } 190 | ) 191 | } 192 | 193 | @inlinable 194 | public static func safeIndex(_ index: Root.Index) -> FunctionalKeyPath 195 | where Root == [Value] 196 | { 197 | FunctionalKeyPath( 198 | embed: { value, root in 199 | reduce(root) { root in 200 | guard 201 | let value = value, 202 | root.indices.contains(index) 203 | else { return } 204 | root[index] = value 205 | } 206 | }, 207 | extract: { root in 208 | root.indices.contains(index) 209 | ? root[index] 210 | : nil 211 | } 212 | ) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Sources/FunctionalModification/Deprecations/Modification.swift: -------------------------------------------------------------------------------- 1 | /// Modifies an object. 2 | /// 3 | /// Returns a new instance for value types 4 | /// Returns modified reference for reference types 5 | @available( 6 | *, 7 | deprecated, 8 | message: "use `reduce(_:with:)` instead." 9 | ) 10 | @inlinable 11 | public func modification( 12 | of object: Object, 13 | with transform: (inout Object) -> Void 14 | ) -> Object { 15 | var _object = object 16 | transform(&_object) 17 | return _object 18 | } 19 | -------------------------------------------------------------------------------- /Sources/FunctionalModification/Reduce.swift: -------------------------------------------------------------------------------- 1 | /// Modifies copy of value and returns it as a result 2 | /// 3 | /// - Returns: 4 | /// - A new instance for value types 5 | /// - Modified object for reference types 6 | @inlinable 7 | public func reduce( 8 | _ value: Value, 9 | with transform: (inout Value) -> Void 10 | ) -> Value { 11 | var _value = value 12 | transform(&_value) 13 | return _value 14 | } 15 | -------------------------------------------------------------------------------- /Tests/DeclarativeConfigurationTests/BuilderTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import FunctionalBuilder 4 | 5 | final class BuilderTests: XCTestCase { 6 | func testBuilder() { 7 | struct TestBuildable: Equatable { 8 | struct Wrapped: Equatable { 9 | var value = 0 10 | } 11 | 12 | var value = false 13 | var wrapped = Wrapped() 14 | } 15 | 16 | let expected: TestBuildable = { 17 | var test = TestBuildable() 18 | test.value = true 19 | test.wrapped.value = 1 20 | return test 21 | }() 22 | 23 | let actual = Builder(TestBuildable()) 24 | .wrapped.value(1) 25 | .value(true) 26 | .build() 27 | 28 | XCTAssertNotEqual(actual, TestBuildable()) 29 | XCTAssertEqual(actual, expected) 30 | } 31 | 32 | func testReinforce() { 33 | struct TestBuildable: Equatable { 34 | struct Wrapped: Equatable { 35 | var value = 0 36 | } 37 | 38 | var value = false 39 | var wrapped = Wrapped() 40 | } 41 | 42 | let expected: TestBuildable = { 43 | var test = TestBuildable() 44 | test.wrapped.value = 1 45 | return test 46 | }() 47 | 48 | var flag = false 49 | 50 | _ = Builder(TestBuildable()) 51 | .wrapped.value(1) 52 | .reinforce { actual in 53 | flag = true 54 | XCTAssertNotEqual(actual, TestBuildable()) 55 | XCTAssertEqual(actual, expected) 56 | } 57 | 58 | XCTAssertEqual(flag, false, "Reinforce transform wasn't called") 59 | } 60 | 61 | func testScope() { 62 | struct Container: BuilderProvider { 63 | class Content { 64 | class InnerClass { 65 | var value: Int = 0 66 | } 67 | 68 | struct InnerStruct { 69 | var value: Int = 0 70 | } 71 | 72 | var a: Int = 0 73 | var b: Int = 0 74 | var c: Int = 0 75 | let innerClass: InnerClass? = nil 76 | var innerStruct: InnerStruct? 77 | 78 | init() {} 79 | } 80 | 81 | let content: Content = .init() 82 | } 83 | 84 | let expected = Container().builder 85 | .content.a(1) 86 | .content.b(2) 87 | .content.c(3) 88 | .content.innerClass.value(1) 89 | .content.innerStruct.value(1) 90 | .build() 91 | 92 | let initial = Container() 93 | let actual = Container().builder 94 | .content.scope { $0 95 | .a(1) 96 | .b(2) 97 | .c(3) 98 | .innerClass 99 | .ifLetScope { $0 100 | .value(1) 101 | } 102 | } 103 | .build() 104 | 105 | XCTAssertNotEqual(actual.content.a, initial.content.a) 106 | XCTAssertNotEqual(actual.content.b, initial.content.b) 107 | XCTAssertNotEqual(actual.content.c, initial.content.c) 108 | XCTAssertEqual(actual.content.innerClass?.value, initial.content.innerClass?.value) 109 | XCTAssertEqual(actual.content.innerStruct?.value, initial.content.innerStruct?.value) 110 | 111 | XCTAssertEqual(actual.content.a, expected.content.a) 112 | XCTAssertEqual(actual.content.b, expected.content.b) 113 | XCTAssertEqual(actual.content.c, expected.content.c) 114 | XCTAssertEqual(actual.content.innerClass?.value, expected.content.innerClass?.value) 115 | XCTAssertEqual(actual.content.innerStruct?.value, expected.content.innerStruct?.value) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Tests/DeclarativeConfigurationTests/ConfiguratorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import FunctionalConfigurator 4 | 5 | final class ConfiguratorTests: XCTestCase { 6 | func testConfiguration() { 7 | struct TestConfigurable: Equatable { 8 | struct Wrapped: Equatable { 9 | var value = 0 10 | } 11 | 12 | var value = false 13 | var wrapped = Wrapped() 14 | } 15 | 16 | let wrappedConfiguator = Configurator() 17 | .wrapped.value(1) 18 | 19 | let valueConfigurator = Configurator() 20 | .value(true) 21 | 22 | let configurator = 23 | wrappedConfiguator 24 | .combined(with: valueConfigurator) 25 | 26 | let initial = TestConfigurable() 27 | let expected = TestConfigurable(value: true, wrapped: .init(value: 1)) 28 | let actual = configurator.configured(initial) 29 | 30 | XCTAssertNotEqual(actual, initial) 31 | XCTAssertEqual(actual, expected) 32 | } 33 | 34 | func testConfigInitializable() { 35 | final class TestConfigurable: NSObject { 36 | override init() { 37 | super.init() 38 | } 39 | 40 | init(value: Bool, wrapped: TestConfigurable.Wrapped) { 41 | self.value = value 42 | self.wrapped = wrapped 43 | } 44 | 45 | struct Wrapped: Equatable { 46 | var value = 0 47 | } 48 | 49 | var value = false 50 | var wrapped = Wrapped() 51 | } 52 | 53 | let initial = TestConfigurable() 54 | let expected = TestConfigurable(value: true, wrapped: .init(value: 1)) 55 | let actual1 = TestConfigurable { 56 | $0 57 | .value(true) 58 | .wrapped(.init(value: 1)) 59 | } 60 | let actual2 = TestConfigurable( 61 | config: TestConfigurable.Config 62 | .value(true) 63 | .wrapped(.init(value: 1)) 64 | ) 65 | 66 | XCTAssertNotEqual(actual1.value, initial.value) 67 | XCTAssertNotEqual(actual1.wrapped, initial.wrapped) 68 | XCTAssertEqual(actual1.value, actual2.value) 69 | XCTAssertEqual(actual1.wrapped, actual2.wrapped) 70 | XCTAssertEqual(actual1.value, expected.value) 71 | XCTAssertEqual(actual1.wrapped, expected.wrapped) 72 | } 73 | 74 | func testCustomConfigurable() { 75 | struct TestConfigurable: CustomConfigurable { 76 | struct Wrapped: Equatable { 77 | var value = 0 78 | } 79 | 80 | var value = false 81 | var wrapped = Wrapped() 82 | } 83 | 84 | let initial = TestConfigurable() 85 | let expected = TestConfigurable(value: true, wrapped: .init(value: 1)) 86 | let actual = TestConfigurable().configured { 87 | $0 88 | .value(true) 89 | .wrapped(.init(value: 1)) 90 | } 91 | 92 | XCTAssertNotEqual(actual.value, initial.value) 93 | XCTAssertNotEqual(actual.wrapped, initial.wrapped) 94 | XCTAssertEqual(actual.value, expected.value) 95 | XCTAssertEqual(actual.wrapped, expected.wrapped) 96 | } 97 | 98 | func testOptional() { 99 | struct TestConfigurable: CustomConfigurable { 100 | internal init(value: Bool = false, wrappedValue: Int = 0) { 101 | self.value = value 102 | wrapped?.value = wrappedValue 103 | } 104 | 105 | class Wrapped: NSObject { 106 | var value: Int? = 0 107 | override init() { self.value = 0 } 108 | } 109 | 110 | var value = false 111 | let _wrapped: Wrapped = .init() 112 | var wrapped: Wrapped? { _wrapped } 113 | } 114 | 115 | let initial = TestConfigurable() 116 | let expected = TestConfigurable(value: true, wrappedValue: 1) 117 | let actual = TestConfigurable().configured { 118 | $0 119 | .value(true) 120 | .wrapped.value(1) 121 | } 122 | 123 | XCTAssertNotEqual(actual.value, initial.value) 124 | XCTAssertNotEqual(actual.wrapped?.value, initial.wrapped?.value) 125 | XCTAssertEqual(actual.value, expected.value) 126 | XCTAssertEqual(actual._wrapped.value, expected._wrapped.value) 127 | } 128 | 129 | func testScope() { 130 | struct Container: ConfigInitializable { 131 | class Content { 132 | class InnerClass { 133 | var value: Int = 0 134 | } 135 | 136 | struct InnerStruct { 137 | var value: Int = 0 138 | } 139 | 140 | var a: Int = 0 141 | var b: Int = 0 142 | var c: Int = 0 143 | let innerClass: InnerClass? = nil 144 | var innerStruct: InnerStruct? 145 | 146 | init() {} 147 | } 148 | 149 | let content: Content = .init() 150 | } 151 | 152 | let expected = Container { 153 | $0 154 | .content.a(1) 155 | .content.b(2) 156 | .content.c(3) 157 | .content.innerClass.value(1) 158 | .content.innerStruct.value(1) 159 | } 160 | let initial = Container() 161 | let actual = Container { 162 | $0 163 | .content.scope { 164 | $0 165 | .a(1) 166 | .b(2) 167 | .c(3) 168 | .innerClass 169 | .ifLetScope { 170 | $0 171 | .value(1) 172 | } 173 | .innerStruct 174 | .ifLetScope { 175 | $0 176 | .value(1) 177 | } 178 | } 179 | } 180 | 181 | XCTAssertNotEqual(actual.content.a, initial.content.a) 182 | XCTAssertNotEqual(actual.content.b, initial.content.b) 183 | XCTAssertNotEqual(actual.content.c, initial.content.c) 184 | XCTAssertEqual(actual.content.innerClass?.value, initial.content.innerClass?.value) 185 | XCTAssertEqual(actual.content.innerStruct?.value, initial.content.innerStruct?.value) 186 | 187 | XCTAssertEqual(actual.content.a, expected.content.a) 188 | XCTAssertEqual(actual.content.b, expected.content.b) 189 | XCTAssertEqual(actual.content.c, expected.content.c) 190 | XCTAssertEqual(actual.content.innerClass?.value, expected.content.innerClass?.value) 191 | XCTAssertEqual(actual.content.innerStruct?.value, expected.content.innerStruct?.value) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Tests/DeclarativeConfigurationTests/FunctionalClosuresTests.swift: -------------------------------------------------------------------------------- 1 | import FunctionalConfigurator 2 | import XCTest 3 | 4 | @testable import FunctionalClosures 5 | 6 | final class FunctionalClosuresTests: XCTestCase { 7 | func testBasicUsage() { 8 | class Object: NSObject { 9 | @DataSource<(Int, Int), Int> 10 | var sum = .init { $0 + $1 } // You can specify default handler 11 | 12 | @Handler1 13 | var handleSumResult // or leave it nil 14 | 15 | func sumOf(_ a: Int, _ b: Int) -> Int? { 16 | let result = _sum((a, b)) 17 | if let result = result { 18 | _handleSumResult(result) 19 | } 20 | return result 21 | } 22 | } 23 | 24 | let object = Object() 25 | let a = 10 26 | let b = 20 27 | let c = 30 28 | var storageForHandler: Int? 29 | 30 | object.handleSumResult { int in 31 | storageForHandler = int 32 | XCTAssertEqual(int, c) 33 | } 34 | 35 | // object._handleSumResult(0) // private 36 | object.$handleSumResult!(c) 37 | XCTAssertEqual(storageForHandler, c) 38 | storageForHandler = nil 39 | 40 | XCTAssertEqual(object.sumOf(a, b), c) 41 | XCTAssertEqual(storageForHandler, c) 42 | storageForHandler = nil 43 | 44 | // object._sum(a, b) // private 45 | XCTAssertEqual(object.$sum((a, b)), c) 46 | XCTAssertEqual(storageForHandler, nil) 47 | 48 | object.handleSumResult(perform: nil) 49 | XCTAssertEqual(storageForHandler, nil) 50 | } 51 | 52 | func testUsageWithBuilder() { 53 | final class Object: ConfigInitializable { 54 | @DataSource<(Int, Int), Int> 55 | var sum = .init { $0 + $1 } // You can specify default handler 56 | 57 | @Handler3 58 | var handleSum // or leave it nil 59 | 60 | init() {} 61 | 62 | func sumOf(_ a: Int, _ b: Int) -> Int? { 63 | let result = _sum((a, b)) 64 | if let result = result { 65 | _handleSum(a, b, result) 66 | } 67 | return result 68 | } 69 | } 70 | 71 | class Storage { 72 | var result: Int = 0 73 | } 74 | 75 | let storage = Storage() 76 | 77 | let object = 78 | Object { 79 | $0 80 | // Handle only result 81 | .$handleSum(assignThird(to: storage, \.result)) 82 | } 83 | 84 | let a = 10 85 | let b = 20 86 | let c = 30 87 | 88 | XCTAssert(object.$handleSum != nil) 89 | XCTAssertEqual(object.sumOf(a, b), c) 90 | XCTAssertEqual(storage.result, c) 91 | 92 | // handle all values 93 | object.handleSum { _a, _b, _c in 94 | XCTAssertEqual(_a, a) 95 | XCTAssertEqual(_b, b) 96 | XCTAssertEqual(_c, c) 97 | } 98 | 99 | object.$handleSum?(a, b, c) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Tests/DeclarativeConfigurationTests/ModificationTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import FunctionalModification 4 | 5 | final class ModificationTests: XCTestCase { 6 | func testValueModificationForValueTypes() { 7 | struct _Test { var value = 0 } 8 | let initial = _Test(value: 0) 9 | let expected = _Test(value: 1) 10 | let actual = reduce(initial) { $0.value = expected.value } 11 | XCTAssertNotEqual(initial.value, expected.value) 12 | XCTAssertEqual(actual.value, expected.value) 13 | } 14 | 15 | func testInstanceModificationForValueTypes() { 16 | struct _Test { var value = 0 } 17 | let initial = _Test(value: 0) 18 | let expected = _Test(value: 1) 19 | let actual = reduce(initial) { $0 = expected } 20 | XCTAssertNotEqual(initial.value, expected.value) 21 | XCTAssertEqual(actual.value, expected.value) 22 | } 23 | 24 | func testValueModificationForReferenceTypes() { 25 | class _Test { var value = 0 } 26 | let initial = _Test() 27 | let expected: _Test = { 28 | let test = _Test() 29 | test.value = 1 30 | return test 31 | }() 32 | 33 | let actual = reduce(initial) { $0.value = expected.value } 34 | XCTAssertEqual(ObjectIdentifier(actual), ObjectIdentifier(initial)) 35 | XCTAssertEqual(actual.value, expected.value) 36 | } 37 | 38 | func testInstanceModificationForReferenceTypes() { 39 | class _Test { var value = 0 } 40 | let initial = _Test() 41 | let expected: _Test = { 42 | let test = _Test() 43 | test.value = 1 44 | return test 45 | }() 46 | 47 | let actual = reduce(initial) { $0 = expected } 48 | XCTAssertNotEqual(ObjectIdentifier(initial), ObjectIdentifier(expected)) 49 | XCTAssertEqual(ObjectIdentifier(actual), ObjectIdentifier(expected)) 50 | } 51 | } 52 | --------------------------------------------------------------------------------