Skip to content

Commit c8c11fb

Browse files
committed
feat: Extended support for optionals & general improvements
1 parent 1f90ff1 commit c8c11fb

File tree

8 files changed

+312
-82
lines changed

8 files changed

+312
-82
lines changed

README.md

+66-33
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Swift Declarative Configuration
22

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)
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-success.svg?style=flat)](https://swift.org/package-manager/) [![@maximkrouk](https://img.shields.io/badge/contact-@maximkrouk-#1DA1F2.svg?style=flat&logo=twitter)](https://twitter.com/maximkrouk)
44

55
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.
66

@@ -12,47 +12,69 @@ Swift Declarative Configuration (SDC, for short) is a tiny library, that enables
1212

1313
- **[FunctionalKeyPath](./Sources/FunctionalKeyPath)** & **[CasePaths](https://github.com/pointfreeco/swift-case-paths)**
1414

15-
KeyPath functional wrappers, one is generalized and the other is for enums. [CasePath is a dependency](https://github.com/pointfreeco/swift-case-paths).
15+
KeyPath functional wrappers, one is generalized and the other is for enums. _[CasePath is a dependency](https://github.com/pointfreeco/swift-case-paths)_.
1616

1717
- **[FunctionalConfigurator](./Sources/FunctionalConfigurator)**
1818

1919
Funtional configurator for anything, enables you to specify modification of an object and to apply the modification later.
2020

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+
2123
- **[FunctionalBuilder](./Sources/FunctionalBuilder)**
2224

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.
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.
2426

2527
- **[DeclarativeConfiguration](./Sources/DeclarativeConfiguration)**
2628

2729
Wraps and exports all the products.
2830

2931
## Basic Usage
3032

31-
### UIKit & FunctionalConfigurator
33+
### UIKit & No SDC
3234

33-
Maybe it worth to make another abstraction over configurator for UI setup, but for example I'll be using pure version.
35+
```swift
36+
class ImageViewController: UIViewController {
37+
let imageView = UIImageView()
38+
39+
override func loadView() {
40+
self.view = imageView
41+
}
42+
43+
override func viewDidLoad() {
44+
super.viewDidLoad()
45+
imageView.contentMode = .scaleAspectFit
46+
imageView.backgroundColor = .black
47+
imageView.layer.masksToBounds = true
48+
imageView.layer.cornerRadius = 10
49+
}
50+
}
51+
```
52+
53+
### UIKit & FunctionalConfigurator
3454

3555
```swift
3656
import FunctionalConfigurator
3757

3858
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-
}
4659

47-
let imageView = UIImageView(config: StyleSheet.imageView)
60+
let imageView = UIImageView { $0
61+
.contentMode(.scaleAspectFit)
62+
.backgroundColor(.black)
63+
.layer.masksToBounds(true)
64+
.layer.cornerRadius(10)
65+
}
4866

4967
override func loadView() {
5068
self.view = imageView
5169
}
70+
5271
}
5372
```
5473

74+
**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.
75+
5576
### UIKit & FunctionalBuilder
77+
5678
```swift
5779
import FunctionalBuilder
5880

@@ -70,40 +92,51 @@ class ImageViewController: UIViewController {
7092
}
7193
```
7294

73-
### Modification
95+
Note: This way is recommended too, and it is more **safe**, because it modifies existing objects.
96+
97+
### Other usecases
98+
99+
#### Builder
100+
101+
Customize any object by passing initial value to a builder
74102

75103
```swift
76-
import FunctionalModification
104+
let object = Builder(Object())
105+
.property.subproperty(value)
106+
.build() // Returns modified object
107+
```
77108

78-
struct MyModel {
79-
var value1 = 0
80-
init() {}
81-
}
109+
For classes you can avoid returning a value by calling `apply` method, instead of `build`
82110

83-
let model_0 = MyModel()
84-
let model_1 = modification(of: model_0) { $0.value = 1 }
111+
```swift
112+
let _class = _Class()
113+
Builder(_class)
114+
.property.subproperty(value)
115+
.apply() // Returns Void
116+
```
85117

86-
import UIKit
118+
Conform your own types to `BuilderProvider` protocol to access builder property.
87119

88-
extension UIView {
89-
@discardableResult
90-
func cornerRadius(_ value: CGFloat) -> Self {
91-
modification(of: self) { view in
92-
view.layer.cornerRadius = value
93-
view.layer.masksToBounds = true
94-
}
95-
}
96-
}
120+
```swift
121+
import CoreLocation
122+
import DeclarativeConfiguration
123+
124+
extension CLLocationCoordinate2D: BuilderProvider {}
125+
// Now you can access `location.builder.latitude(0).build()`
97126
```
98127

128+
#### Configurator
129+
130+
> README PLACEHOLDER (Not yet written 😅)
131+
99132
## Installation
100133

101134
### Basic
102135

103136
You can add DeclarativeConfiguration to an Xcode project by adding it as a package dependency.
104137

105138
1. From the **File** menu, select **Swift Packages › Add Package Dependency…**
106-
2. Enter "https://github.com/makeupstudio/swift-declarative-configuration" into the package repository URL text field
139+
2. Enter [`"https://github.com/makeupstudio/swift-declarative-configuration"`](https://github.com/makeupstudio/swift-declarative-configuration) into the package repository URL text field
107140
3. Choose products you need to link them to your project.
108141

109142
### Recommended
@@ -113,7 +146,7 @@ If you use SwiftPM for your project, you can add DeclarativeConfiguration to you
113146
```swift
114147
.package(
115148
url: "git@github.com:makeupstudio/swift-declarative-configuration.git",
116-
from: "0.0.2"
149+
from: "0.0.4"
117150
)
118151
```
119152

Sources/FunctionalBuilder/Builder.swift

+72-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public struct Builder<Base> {
66
private var _initialValue: () -> Base
77
private var _configurator: Configurator<Base>
88

9-
public func build() -> Base { _configurator.configure(_initialValue()) }
9+
public func build() -> Base { _configurator.configured(_initialValue()) }
1010

1111
@inlinable
1212
public func apply() where Base: AnyObject { _ = build() }
@@ -69,21 +69,39 @@ public struct Builder<Base> {
6969
public subscript<Value>(
7070
dynamicMember keyPath: WritableKeyPath<Base, Value>
7171
) -> CallableBlock<Value> {
72-
.init(
72+
CallableBlock<Value>(
7373
builder: self,
74-
keyPath: .init(keyPath)
74+
keyPath: FunctionalKeyPath(keyPath)
7575
)
7676
}
7777

7878
public subscript<Value>(
7979
dynamicMember keyPath: KeyPath<Base, Value>
80-
) -> NonCallableBlock<Value> where Base: AnyObject, Value: AnyObject {
81-
.init(
80+
) -> NonCallableBlock<Value> {
81+
NonCallableBlock<Value>(
8282
builder: self,
8383
keyPath: .getonly(keyPath)
8484
)
8585
}
8686

87+
public subscript<Wrapped, Value>(
88+
dynamicMember keyPath: WritableKeyPath<Wrapped, Value>
89+
) -> CallableBlock<Value?> where Base == Optional<Wrapped> {
90+
CallableBlock<Value?>(
91+
builder: self,
92+
keyPath: FunctionalKeyPath(keyPath).optional()
93+
)
94+
}
95+
96+
public subscript<Wrapped, Value>(
97+
dynamicMember keyPath: KeyPath<Wrapped, Value>
98+
) -> NonCallableBlock<Value?> where Base == Optional<Wrapped> {
99+
NonCallableBlock<Value?>(
100+
builder: self,
101+
keyPath: FunctionalKeyPath.getonly(keyPath).optional()
102+
)
103+
}
104+
87105
}
88106

89107
extension Builder {
@@ -122,17 +140,39 @@ extension Builder {
122140
public subscript<LocalValue>(
123141
dynamicMember keyPath: WritableKeyPath<Value, LocalValue>
124142
) -> CallableBlock<LocalValue> {
125-
.init(
143+
CallableBlock<LocalValue>(
126144
builder: _block.builder,
127145
keyPath: _block.keyPath.appending(path: .init(keyPath))
128146
)
129147
}
130148

131149
public subscript<LocalValue>(
132150
dynamicMember keyPath: KeyPath<Value, LocalValue>
133-
) -> NonCallableBlock<LocalValue> where Value: AnyObject, LocalValue: AnyObject {
151+
) -> NonCallableBlock<LocalValue> {
134152
_block[dynamicMember: keyPath]
135153
}
154+
155+
public subscript<Wrapped, LocalValue>(
156+
dynamicMember keyPath: WritableKeyPath<Wrapped, LocalValue>
157+
) -> CallableBlock<LocalValue?> where Value == Optional<Wrapped> {
158+
CallableBlock<LocalValue?>(
159+
builder: _block.builder,
160+
keyPath: _block.keyPath.appending(
161+
path: FunctionalKeyPath(keyPath).optional()
162+
)
163+
)
164+
}
165+
166+
public subscript<Wrapped, LocalValue>(
167+
dynamicMember keyPath: KeyPath<Wrapped, LocalValue>
168+
) -> NonCallableBlock<LocalValue?> where Value == Optional<Wrapped> {
169+
NonCallableBlock<LocalValue?>(
170+
builder: _block.builder,
171+
keyPath: _block.keyPath.appending(
172+
path: FunctionalKeyPath.getonly(keyPath).optional()
173+
)
174+
)
175+
}
136176
}
137177

138178
@dynamicMemberLookup
@@ -143,19 +183,41 @@ extension Builder {
143183
public subscript<LocalValue>(
144184
dynamicMember keyPath: WritableKeyPath<Value, LocalValue>
145185
) -> CallableBlock<LocalValue> where Value: AnyObject {
146-
.init(
186+
CallableBlock<LocalValue>(
147187
builder: self.builder,
148188
keyPath: self.keyPath.appending(path: .init(keyPath))
149189
)
150190
}
151191

152192
public subscript<LocalValue>(
153193
dynamicMember keyPath: KeyPath<Value, LocalValue>
154-
) -> NonCallableBlock<LocalValue> where Value: AnyObject, LocalValue: AnyObject {
155-
.init(
194+
) -> NonCallableBlock<LocalValue> {
195+
NonCallableBlock<LocalValue>(
156196
builder: self.builder,
157197
keyPath: self.keyPath.appending(path: .getonly(keyPath))
158198
)
159199
}
200+
201+
public subscript<Wrapped, LocalValue>(
202+
dynamicMember keyPath: WritableKeyPath<Wrapped, LocalValue>
203+
) -> CallableBlock<LocalValue?> where Wrapped: AnyObject, Value == Optional<Wrapped> {
204+
CallableBlock<LocalValue?>(
205+
builder: self.builder,
206+
keyPath: self.keyPath.appending(
207+
path: FunctionalKeyPath(keyPath).optional()
208+
)
209+
)
210+
}
211+
212+
public subscript<Wrapped, LocalValue>(
213+
dynamicMember keyPath: KeyPath<Wrapped, LocalValue>
214+
) -> NonCallableBlock<LocalValue?> where Value == Optional<Wrapped> {
215+
NonCallableBlock<LocalValue?>(
216+
builder: self.builder,
217+
keyPath: self.keyPath.appending(
218+
path: FunctionalKeyPath.getonly(keyPath).optional()
219+
)
220+
)
221+
}
160222
}
161223
}

Sources/FunctionalConfigurator/ConfigIntializable.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ extension ConfigInitializable {
1111
/// Instantiates a new object with specified configuration
1212
///
1313
/// Note: Type must implement custom intializer with no parameters, even if it inherits from NSObject
14-
public init(_ configuration: (Config) -> Config) {
15-
self.init(configuration(Config()))
14+
public init(config configuration: (Config) -> Config) {
15+
self.init(config: configuration(Config()))
1616
}
1717

1818
/// Instantiates a new object with specified configuration
1919
///
2020
/// Note: Type must implement custom intializer with no parameters, even if it inherits from NSObject
21-
public init(_ configurator: Config) {
22-
self = configurator.configure(.init())
21+
public init(config configurator: Config) {
22+
self = configurator.configured(.init())
2323
}
2424
}
2525

0 commit comments

Comments
 (0)