Skip to content

Commit

Permalink
feat: Extended support for optionals & general improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
maximkrouk committed Oct 18, 2020
1 parent 1f90ff1 commit c8c11fb
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 82 deletions.
99 changes: 66 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Swift Declarative Configuration

[![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)
[![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)

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.

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

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

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

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

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

Also contains self-implementing protocols (`ConfigInitializable`, `CustomConfigurable`) to enable you add custom configuration support for your types (`NSObject` already conforms to it for you).

- **[FunctionalBuilder](./Sources/FunctionalBuilder)**

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

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

Wraps and exports all the products.

## Basic Usage

### UIKit & FunctionalConfigurator
### UIKit & No SDC

Maybe it worth to make another abstraction over configurator for UI setup, but for example I'll be using pure version.
```swift
class ImageViewController: UIViewController {
let imageView = UIImageView()

override func loadView() {
self.view = imageView
}

override func viewDidLoad() {
super.viewDidLoad()
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .black
imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = 10
}
}
```

### UIKit & FunctionalConfigurator

```swift
import FunctionalConfigurator

class ImageViewController: UIViewController {
enum StyleSheet {
static let imageView = Configurator<UIImageView>
.contentMode(.scaleAspectFit)
.backgroundColor(.black)
.layer.masksToBounds(true)
.layer.cornerRadius(10)
}

let imageView = UIImageView(config: StyleSheet.imageView)
let imageView = UIImageView { $0
.contentMode(.scaleAspectFit)
.backgroundColor(.black)
.layer.masksToBounds(true)
.layer.cornerRadius(10)
}

override func loadView() {
self.view = imageView
}

}
```

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

### UIKit & FunctionalBuilder

```swift
import FunctionalBuilder

Expand All @@ -70,40 +92,51 @@ class ImageViewController: UIViewController {
}
```

### Modification
Note: This way is recommended too, and it is more **safe**, because it modifies existing objects.

### Other usecases

#### Builder

Customize any object by passing initial value to a builder

```swift
import FunctionalModification
let object = Builder(Object())
.property.subproperty(value)
.build() // Returns modified object
```

struct MyModel {
var value1 = 0
init() {}
}
For classes you can avoid returning a value by calling `apply` method, instead of `build`

let model_0 = MyModel()
let model_1 = modification(of: model_0) { $0.value = 1 }
```swift
let _class = _Class()
Builder(_class)
.property.subproperty(value)
.apply() // Returns Void
```

import UIKit
Conform your own types to `BuilderProvider` protocol to access builder property.

extension UIView {
@discardableResult
func cornerRadius(_ value: CGFloat) -> Self {
modification(of: self) { view in
view.layer.cornerRadius = value
view.layer.masksToBounds = true
}
}
}
```swift
import CoreLocation
import DeclarativeConfiguration

extension CLLocationCoordinate2D: BuilderProvider {}
// Now you can access `location.builder.latitude(0).build()`
```

#### Configurator

> README PLACEHOLDER (Not yet written 😅)
## Installation

### Basic

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

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

### Recommended
Expand All @@ -113,7 +146,7 @@ If you use SwiftPM for your project, you can add DeclarativeConfiguration to you
```swift
.package(
url: "[email protected]:makeupstudio/swift-declarative-configuration.git",
from: "0.0.2"
from: "0.0.4"
)
```

Expand Down
82 changes: 72 additions & 10 deletions Sources/FunctionalBuilder/Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public struct Builder<Base> {
private var _initialValue: () -> Base
private var _configurator: Configurator<Base>

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

@inlinable
public func apply() where Base: AnyObject { _ = build() }
Expand Down Expand Up @@ -69,21 +69,39 @@ public struct Builder<Base> {
public subscript<Value>(
dynamicMember keyPath: WritableKeyPath<Base, Value>
) -> CallableBlock<Value> {
.init(
CallableBlock<Value>(
builder: self,
keyPath: .init(keyPath)
keyPath: FunctionalKeyPath(keyPath)
)
}

public subscript<Value>(
dynamicMember keyPath: KeyPath<Base, Value>
) -> NonCallableBlock<Value> where Base: AnyObject, Value: AnyObject {
.init(
) -> NonCallableBlock<Value> {
NonCallableBlock<Value>(
builder: self,
keyPath: .getonly(keyPath)
)
}

public subscript<Wrapped, Value>(
dynamicMember keyPath: WritableKeyPath<Wrapped, Value>
) -> CallableBlock<Value?> where Base == Optional<Wrapped> {
CallableBlock<Value?>(
builder: self,
keyPath: FunctionalKeyPath(keyPath).optional()
)
}

public subscript<Wrapped, Value>(
dynamicMember keyPath: KeyPath<Wrapped, Value>
) -> NonCallableBlock<Value?> where Base == Optional<Wrapped> {
NonCallableBlock<Value?>(
builder: self,
keyPath: FunctionalKeyPath.getonly(keyPath).optional()
)
}

}

extension Builder {
Expand Down Expand Up @@ -122,17 +140,39 @@ extension Builder {
public subscript<LocalValue>(
dynamicMember keyPath: WritableKeyPath<Value, LocalValue>
) -> CallableBlock<LocalValue> {
.init(
CallableBlock<LocalValue>(
builder: _block.builder,
keyPath: _block.keyPath.appending(path: .init(keyPath))
)
}

public subscript<LocalValue>(
dynamicMember keyPath: KeyPath<Value, LocalValue>
) -> NonCallableBlock<LocalValue> where Value: AnyObject, LocalValue: AnyObject {
) -> NonCallableBlock<LocalValue> {
_block[dynamicMember: keyPath]
}

public subscript<Wrapped, LocalValue>(
dynamicMember keyPath: WritableKeyPath<Wrapped, LocalValue>
) -> CallableBlock<LocalValue?> where Value == Optional<Wrapped> {
CallableBlock<LocalValue?>(
builder: _block.builder,
keyPath: _block.keyPath.appending(
path: FunctionalKeyPath(keyPath).optional()
)
)
}

public subscript<Wrapped, LocalValue>(
dynamicMember keyPath: KeyPath<Wrapped, LocalValue>
) -> NonCallableBlock<LocalValue?> where Value == Optional<Wrapped> {
NonCallableBlock<LocalValue?>(
builder: _block.builder,
keyPath: _block.keyPath.appending(
path: FunctionalKeyPath.getonly(keyPath).optional()
)
)
}
}

@dynamicMemberLookup
Expand All @@ -143,19 +183,41 @@ extension Builder {
public subscript<LocalValue>(
dynamicMember keyPath: WritableKeyPath<Value, LocalValue>
) -> CallableBlock<LocalValue> where Value: AnyObject {
.init(
CallableBlock<LocalValue>(
builder: self.builder,
keyPath: self.keyPath.appending(path: .init(keyPath))
)
}

public subscript<LocalValue>(
dynamicMember keyPath: KeyPath<Value, LocalValue>
) -> NonCallableBlock<LocalValue> where Value: AnyObject, LocalValue: AnyObject {
.init(
) -> NonCallableBlock<LocalValue> {
NonCallableBlock<LocalValue>(
builder: self.builder,
keyPath: self.keyPath.appending(path: .getonly(keyPath))
)
}

public subscript<Wrapped, LocalValue>(
dynamicMember keyPath: WritableKeyPath<Wrapped, LocalValue>
) -> CallableBlock<LocalValue?> where Wrapped: AnyObject, Value == Optional<Wrapped> {
CallableBlock<LocalValue?>(
builder: self.builder,
keyPath: self.keyPath.appending(
path: FunctionalKeyPath(keyPath).optional()
)
)
}

public subscript<Wrapped, LocalValue>(
dynamicMember keyPath: KeyPath<Wrapped, LocalValue>
) -> NonCallableBlock<LocalValue?> where Value == Optional<Wrapped> {
NonCallableBlock<LocalValue?>(
builder: self.builder,
keyPath: self.keyPath.appending(
path: FunctionalKeyPath.getonly(keyPath).optional()
)
)
}
}
}
8 changes: 4 additions & 4 deletions Sources/FunctionalConfigurator/ConfigIntializable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ extension ConfigInitializable {
/// Instantiates a new object with specified configuration
///
/// Note: Type must implement custom intializer with no parameters, even if it inherits from NSObject
public init(_ configuration: (Config) -> Config) {
self.init(configuration(Config()))
public init(config configuration: (Config) -> Config) {
self.init(config: configuration(Config()))
}

/// Instantiates a new object with specified configuration
///
/// Note: Type must implement custom intializer with no parameters, even if it inherits from NSObject
public init(_ configurator: Config) {
self = configurator.configure(.init())
public init(config configurator: Config) {
self = configurator.configured(.init())
}
}

Expand Down
Loading

0 comments on commit c8c11fb

Please sign in to comment.