Skip to content

Instantly share code, notes, and snippets.

@maximkrouk
Last active May 15, 2022 08:36
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maximkrouk/eede7171952e044492c1fa57291bcf94 to your computer and use it in GitHub Desktop.
Save maximkrouk/eede7171952e044492c1fa57291bcf94 to your computer and use it in GitHub Desktop.
Functional generic builder
// NOTE: Depends on Modifications.swift
// NOTE: Depends on BuildBlocks.swift
// NOTE: Depends on FunctionalKeyPath https://gist.github.com/maximkrouk/6287fb56321a21e8180d5fe044e642e4
import Foundation
@dynamicMemberLookup
public struct Builder<Object> {
private var _build: () -> Object
public func build() -> Object { _build() }
public init(_ initialValue: Object) { self._build = { initialValue } }
@inlinable
public func setIf<Value>(_ condition: Bool, _ keyPath: WritableKeyPath<Object, Value>, _ value: @autoclosure () -> Value) -> Self {
if condition { return self.set(keyPath, value()) }
else { return self }
}
@inlinable
public func setIf(_ condition: Bool, _ transform: @escaping (inout Object) -> Void) -> Self {
if condition { return self.set(transform) }
else { return self }
}
@inlinable
public func set<Value>(_ keyPath: WritableKeyPath<Object, Value>, _ value: Value) -> Self {
self.set { object in
object[keyPath: keyPath] = value
}
}
public func set(_ transform: @escaping (inout Object) -> Void) -> Self {
modification(of: self) { _self in
_self._build = {
modification(of: build(), with: transform)
}
}
}
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Object, T>) -> CallableBuildBlock<T> {
CallableBuildBlock(
base: BuildBlock(
builder: self,
keyPath: .init(keyPath)
)
)
}
public subscript<T>(dynamicMember keyPath: ReferenceWritableKeyPath<Object, T>) -> CallableBuildBlock<T>
where T: AnyObject {
CallableBuildBlock(
base: BuildBlock(
builder: self,
keyPath: .init(keyPath)
)
)
}
public subscript<T>(dynamicMember keyPath: KeyPath<Object, T>) -> BuildBlock<T>
where T: AnyObject {
BuildBlock<T>(
builder: self,
keyPath: FunctionalKeyPath(
embed: { value, root in root },
extract: { root in root[keyPath: keyPath] }
)
)
}
}
public protocol BuilderProvider {}
extension BuilderProvider {
public var builder: Builder<Self> { .init(self) }
}
extension NSObject: BuilderProvider {}
// NOTE: Depends on Modifications.swift
// NOTE: Depends on FunctionalKeyPath https://gist.github.com/maximkrouk/6287fb56321a21e8180d5fe044e642e4
extension Builder {
@dynamicMemberLookup
public struct CallableBuildBlock<Value> {
var base: BuildBlock<Value>
public func callAsFunction(_ value: Value) -> Builder<Object> {
base.builder.set { object in
object = base.keyPath.embed(value, in: object)
}
}
public subscript<T>(dynamicMember keyPath: ReferenceWritableKeyPath<Value, T>) -> CallableBuildBlock<T>
where Value: AnyObject { base[dynamicMember: keyPath] }
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> CallableBuildBlock<T> {
base[dynamicMember: keyPath]
}
public subscript<U, T>(dynamicMember keyPath: WritableKeyPath<U, T>) -> CallableBuildBlock<T?>
where Value == Optional<U> { base[dynamicMember: keyPath] }
public subscript<T>(dynamicMember keyPath: KeyPath<Object, T>) -> BuildBlock<T>
where T: AnyObject { base[dynamicMember: keyPath] }
}
@dynamicMemberLookup
public struct BuildBlock<Value> {
internal init(builder: Builder<Object>, keyPath: FunctionalKeyPath<Object, Value>) {
self.builder = builder
self.keyPath = keyPath
}
var builder: Builder<Object>
var keyPath: FunctionalKeyPath<Object, Value>
public subscript<T>(dynamicMember keyPath: ReferenceWritableKeyPath<Value, T>) -> CallableBuildBlock<T>
where Value: AnyObject {
CallableBuildBlock<T>(
base: BuildBlock<T>(
builder: builder,
keyPath: self.keyPath.appending(
path: FunctionalKeyPath<Value, T>(keyPath)
)
)
)
}
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> CallableBuildBlock<T> {
CallableBuildBlock<T>(
base: BuildBlock<T>(
builder: builder,
keyPath: self.keyPath.appending(
path: FunctionalKeyPath<Value, T>(keyPath)
)
)
)
}
public subscript<U, T>(dynamicMember keyPath: WritableKeyPath<U, T>) -> CallableBuildBlock<T?>
where Value == Optional<U> {
CallableBuildBlock<T?>(
base: BuildBlock<T?>(
builder: builder,
keyPath: self.keyPath.appending(
path: FunctionalKeyPath<U?, T?>(
embed: { value, root in
modification(of: root) { root in
value.map { root?[keyPath: keyPath] = $0 }
}
},
extract: { root in
root?[keyPath: keyPath]
}
)
)
)
)
}
public subscript<T>(dynamicMember keyPath: KeyPath<Object, T>) -> BuildBlock<T>
where T: AnyObject {
BuildBlock<T>(
builder: builder,
keyPath: .init(
embed: { value, root in root },
extract: { root in root[keyPath: keyPath] }
)
)
}
}
}
@inlinable
public func modification<Object>(
of object: Object,
transform: (inout Object) throws -> Void
) rethrows -> Object {
var object = object
try transform(&object)
return object
}
@maximkrouk
Copy link
Author

maximkrouk commented Mar 25, 2020

https://github.com/makeupstudio/swift-declarative-configuration

Dependencies

Usage

struct Test {
    var title: String = ""
    var description: String = ""
    var date: Date = .init()
    var action: () -> Void = {}
}

func makeSecretTest() -> Test {
    Builder(Test())
        .title( "Secret test")
        .description("Nobody should know")
        .action { print("secret action") }
        .build()
}

let label = UILabel().builder
    .frame.size.width(100)
    .backgroundColor(.red)
    .textColor(.white)
    .text("Hello")
    .build()

// only for reference types
label.builder.alpha(0.8).apply()

Back to index

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment