Skip to content

Commit 8e2e98e

Browse files
committed
Initial commit
0 parents  commit 8e2e98e

File tree

14 files changed

+749
-0
lines changed

14 files changed

+749
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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.

Package.swift

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// swift-tools-version:5.3
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "swift-declarative-configuration",
7+
products: [
8+
.library(
9+
name: "DeclarativeConfiguration",
10+
targets: ["DeclarativeConfiguration"]
11+
),
12+
.library(
13+
name: "FunctionalBuilder",
14+
targets: ["FunctionalBuilder"]
15+
),
16+
.library(
17+
name: "FunctionalConfigurator",
18+
targets: ["FunctionalConfigurator"]
19+
),
20+
.library(
21+
name: "FunctionalKeyPath",
22+
targets: ["FunctionalKeyPath"]
23+
),
24+
.library(
25+
name: "FunctionalModification",
26+
targets: ["FunctionalModification"]
27+
),
28+
],
29+
dependencies: [
30+
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "0.1.2")
31+
],
32+
targets: [
33+
.target(
34+
name: "DeclarativeConfiguration",
35+
dependencies: [
36+
.target(name: "FunctionalBuilder"),
37+
.target(name: "FunctionalConfigurator"),
38+
.target(name: "FunctionalKeyPath"),
39+
.target(name: "FunctionalModification"),
40+
.product(name: "CasePaths", package: "swift-case-paths")
41+
]
42+
),
43+
.target(
44+
name: "FunctionalBuilder",
45+
dependencies: [
46+
.target(name: "FunctionalConfigurator"),
47+
.target(name: "FunctionalKeyPath"),
48+
.target(name: "FunctionalModification")
49+
]
50+
),
51+
.target(
52+
name: "FunctionalConfigurator",
53+
dependencies: [
54+
.target(name: "FunctionalKeyPath"),
55+
.target(name: "FunctionalModification")
56+
]
57+
),
58+
.target(name: "FunctionalKeyPath"),
59+
.target(name: "FunctionalModification"),
60+
.testTarget(
61+
name: "FunctionalConfigurationTests",
62+
dependencies: [
63+
.target(name: "FunctionalBuilder")
64+
]
65+
),
66+
]
67+
)

README.md

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Swift Declarative Configuration
2+
3+
[![Swift 5.3](https://img.shields.io/badge/swift-5.3-ED523F.svg?style=flat)](https://swift.org/download/) [![SwiftPM](https://img.shields.io/badge/SwiftPM-ED523F.svg?style=flat)](https://swift.org/package-manager/) [![@maximkrouk](https://img.shields.io/badge/contact-@maximkrouk-ED523F.svg?style=flat)](https://twitter.com/maximkrouk)
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 modificating methods should return modified `self`
12+
13+
- **[FunctionalKeyPath](./Sources/FunctionalKeyPath)** & **[CasePaths](https://github.com/pointfreeco/swift-case-paths)**
14+
15+
KeyPath functional wrappers, one is generalized and the other is for enums. [CasePath is a dependency](https://github.com/pointfreeco/swift-case-paths).
16+
17+
- **[FunctionalConfigurator](./Sources/FunctionalConfigurator)**
18+
19+
Funtional configurator for anything, enables you to specify modification of an object and to apply the modification later.
20+
21+
- **[FunctionalBuilder](./Sources/FunctionalBuilder)**
22+
23+
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.
24+
25+
- **[DeclarativeConfiguration](./Sources/DeclarativeConfiguration)**
26+
27+
Wraps and exports all the products.
28+
29+
## Basic Usage
30+
31+
### UIKit & FunctionalConfigurator
32+
33+
Maybe it worth to make another abstraction over configurator for UI setup, but for example I'll be using pure version.
34+
35+
```swift
36+
import FunctionalConfigurator
37+
38+
class ImageViewController: UIViewController {
39+
enum StyleSheet {
40+
static let imageView = Configurator<UIImageView>
41+
.contentMode(.scaleAspectFit)
42+
.backgroundColor(.black)
43+
.layer.masksToBounds(true)
44+
.layer.cornerRadius(10)
45+
}
46+
47+
let imageView: UIImageView = .init()
48+
49+
override func loadView() {
50+
self.view = imageView
51+
}
52+
53+
override func viewDidLoad() {
54+
super.viewDidLoad()
55+
StyleSheet.imageView.configure(imageView)
56+
}
57+
}
58+
```
59+
60+
### UIKit & FunctionalBuilder
61+
```swift
62+
import FunctionalBuilder
63+
64+
class ImageViewController: UIViewController {
65+
let imageView = UIImageView().builder
66+
.contentMode(.scaleAspectFit)
67+
.backgroundColor(.black)
68+
.layer.masksToBounds(true)
69+
.layer.cornerRadius(10)
70+
.build()
71+
72+
override func loadView() {
73+
self.view = imageView
74+
}
75+
}
76+
```
77+
78+
### Modification
79+
80+
```swift
81+
import FunctionalModification
82+
83+
struct MyModel {
84+
var value1 = 0
85+
init() {}
86+
}
87+
88+
let model_0 = MyModel()
89+
let model_1 = modification(of: model_0) { $0.value = 1 }
90+
91+
import UIKit
92+
93+
extension UIView {
94+
@discardableResult
95+
func cornerRadius(_ value: CGFloat) -> Self {
96+
modification(of: self) { view in
97+
view.layer.cornerRadius = value
98+
view.layer.masksToBounds = true
99+
}
100+
}
101+
}
102+
```
103+
104+
## Installation
105+
106+
### Basic
107+
108+
You can add DeclarativeConfiguration to an Xcode project by adding it as a package dependency.
109+
110+
1. From the **File** menu, select **Swift Packages › Add Package Dependency…**
111+
2. Enter "https://github.com/makeupstudio/swift-declarative-configuration" into the package repository URL text field
112+
3. Choose products you need to link them to your project.
113+
114+
### Recommended
115+
116+
If you use SwiftPM for your project, you can add DeclarativeConfiguration to your package file. Also my advice will be to use SSH.
117+
118+
```swift
119+
.package(
120+
url: "git@github.com:makeupstudio/swift-declarative-configuration.git",
121+
from: "0.0.1"
122+
)
123+
```
124+
125+
Do not forget about target dependencies:
126+
127+
```swift
128+
.product(
129+
name: "DeclarativeConfiguration",
130+
package: "swift-declarative-configuration"
131+
)
132+
```
133+
134+
## License
135+
136+
This library is released under the MIT license. See [LICENSE](./LICENSE) for details.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@_exported import FunctionalBuilder
2+
@_exported import FunctionalConfigurator
3+
@_exported import FunctionalKeyPath
4+
@_exported import FunctionalModification
5+
@_exported import CasePaths
+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import FunctionalConfigurator
2+
import FunctionalKeyPath
3+
4+
@dynamicMemberLookup
5+
public struct Builder<Base> {
6+
private var _initialValue: () -> Base
7+
private var _configurator: Configurator<Base>
8+
9+
public func build() -> Base { _configurator.configure(_initialValue()) }
10+
11+
@inlinable
12+
public func apply() where Base: AnyObject { _ = build() }
13+
14+
@inlinable
15+
public func reinforce(_ transform: @escaping (inout Base) -> Void) -> Builder {
16+
Builder(build()).set(transform)
17+
}
18+
19+
public init(_ initialValue: @escaping @autoclosure () -> Base) {
20+
self.init(
21+
initialValue,
22+
Configurator<Base>()
23+
)
24+
}
25+
26+
private init(
27+
_ initialValue: @escaping () -> Base,
28+
_ configurator: Configurator<Base>
29+
) {
30+
_initialValue = initialValue
31+
_configurator = configurator
32+
}
33+
34+
public func set(
35+
_ transform: @escaping (inout Base) -> Void
36+
) -> Builder {
37+
Builder(
38+
_initialValue,
39+
_configurator.set(transform)
40+
)
41+
}
42+
43+
public subscript<Value>(
44+
dynamicMember keyPath: WritableKeyPath<Base, Value>
45+
) -> CallableBlock<Value> {
46+
.init(
47+
builder: self,
48+
keyPath: .init(keyPath)
49+
)
50+
}
51+
52+
public subscript<Value>(
53+
dynamicMember keyPath: KeyPath<Base, Value>
54+
) -> NonCallableBlock<Value> where Base: AnyObject, Value: AnyObject {
55+
.init(
56+
builder: self,
57+
keyPath: .getonly(keyPath)
58+
)
59+
}
60+
61+
}
62+
63+
extension Builder {
64+
@dynamicMemberLookup
65+
public struct CallableBlock<Value> {
66+
private var _block: NonCallableBlock<Value>
67+
68+
init(
69+
builder: Builder,
70+
keyPath: FunctionalKeyPath<Base, Value>
71+
) {
72+
self._block = .init(
73+
builder: builder,
74+
keyPath: keyPath
75+
)
76+
}
77+
78+
public func callAsFunction(_ value: @escaping @autoclosure () -> Value) -> Builder {
79+
Builder(
80+
_block.builder._initialValue,
81+
_block.builder._configurator.appendingConfiguration { base in
82+
_block.keyPath.embed(value(), in: base)
83+
}
84+
)
85+
}
86+
87+
public subscript<LocalValue>(
88+
dynamicMember keyPath: WritableKeyPath<Value, LocalValue>
89+
) -> CallableBlock<LocalValue> {
90+
.init(
91+
builder: _block.builder,
92+
keyPath: _block.keyPath.appending(path: .init(keyPath))
93+
)
94+
}
95+
96+
public subscript<LocalValue>(
97+
dynamicMember keyPath: KeyPath<Value, LocalValue>
98+
) -> NonCallableBlock<LocalValue> where Value: AnyObject, LocalValue: AnyObject {
99+
_block[dynamicMember: keyPath]
100+
}
101+
}
102+
103+
@dynamicMemberLookup
104+
public struct NonCallableBlock<Value> {
105+
var builder: Builder
106+
var keyPath: FunctionalKeyPath<Base, Value>
107+
108+
public subscript<LocalValue>(
109+
dynamicMember keyPath: WritableKeyPath<Value, LocalValue>
110+
) -> CallableBlock<LocalValue> where Value: AnyObject {
111+
.init(
112+
builder: self.builder,
113+
keyPath: self.keyPath.appending(path: .init(keyPath))
114+
)
115+
}
116+
117+
public subscript<LocalValue>(
118+
dynamicMember keyPath: KeyPath<Value, LocalValue>
119+
) -> NonCallableBlock<LocalValue> where Value: AnyObject, LocalValue: AnyObject {
120+
.init(
121+
builder: self.builder,
122+
keyPath: self.keyPath.appending(path: .getonly(keyPath))
123+
)
124+
}
125+
}
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Foundation
2+
3+
public protocol BuilderProvider {}
4+
extension BuilderProvider {
5+
public var builder: Builder<Self> { .init(self) }
6+
}
7+
8+
extension NSObject: BuilderProvider {}

0 commit comments

Comments
 (0)