Use gyb tool to implement MapKeyPath
This commit is contained in:
committed by
Sergej Jaskiewicz
parent
d7b9e87f6d
commit
14b7ced2fe
@@ -1,7 +1,71 @@
|
||||
import Danger
|
||||
import Foundation
|
||||
|
||||
extension StringProtocol {
|
||||
func dropSuffix<S: StringProtocol>(_ suffix: S) -> SubSequence {
|
||||
if hasSuffix(suffix) {
|
||||
return self[..<index(endIndex, offsetBy: -suffix.count)]
|
||||
} else {
|
||||
return self[...]
|
||||
}
|
||||
}
|
||||
|
||||
func directoryAndFileName() -> (SubSequence, SubSequence) {
|
||||
let lastPathSeparator = lastIndex(of: "/")
|
||||
if let lastPathSeparator = lastPathSeparator {
|
||||
return (self[..<lastPathSeparator], self[index(after: lastPathSeparator)...])
|
||||
} else {
|
||||
return (".", self[...])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let danger = Danger()
|
||||
|
||||
let allCreatedAndModified = danger.git.createdFiles + danger.git.modifiedFiles
|
||||
|
||||
do {
|
||||
// Fail if the committer modified a GYB template but forgot to run `make gyb`.
|
||||
|
||||
let modifiedTemplates = allCreatedAndModified.filter { $0.hasSuffix(".gyb") }
|
||||
|
||||
for modifiedTemplate in modifiedTemplates {
|
||||
let (directory, filename) = modifiedTemplate.directoryAndFileName()
|
||||
let generated = "\(directory)/GENERATED-\(filename.dropSuffix(".gyb"))"
|
||||
|
||||
if !allCreatedAndModified.contains(generated) {
|
||||
fail("""
|
||||
A template \(modifiedTemplate) was modified, but the file \(generated) \
|
||||
was not regenerated.
|
||||
|
||||
Run `make gyb` from the root of the project and commit the changes.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
// Fail if the committer modified a generated file.
|
||||
// A template should be modified instead.
|
||||
|
||||
for modifiedGeneratedFile in danger.git.modifiedFiles
|
||||
where modifiedGeneratedFile.contains("GENERATED-")
|
||||
{
|
||||
let template = modifiedGeneratedFile
|
||||
.replacingOccurrences(of: "GENERATED-", with: "") + ".gyb"
|
||||
|
||||
if !danger.git.modifiedFiles.contains(template) {
|
||||
fail("""
|
||||
A generated file \(modifiedGeneratedFile) was modified, but \
|
||||
the template it was generated from was not modified.
|
||||
|
||||
Please modify the template \(template) instead, \
|
||||
run `make gyb` from the root of the project and commit the changes.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwiftLint.lint(inline: true,
|
||||
configFile: ".swiftlint.yml",
|
||||
strict: true,
|
||||
|
||||
@@ -28,6 +28,9 @@ generate-compatibility-xcodeproj:
|
||||
generate-xcodeproj:
|
||||
swift package generate-xcodeproj --enable-code-coverage
|
||||
|
||||
gyb:
|
||||
$(shell ./utils/recursively_gyb.sh)
|
||||
|
||||
clean:
|
||||
rm -rf .build
|
||||
|
||||
@@ -38,4 +41,5 @@ clean:
|
||||
test-compatibility-debug \
|
||||
generate-compatibility-xcodeproj \
|
||||
generate-xcodeproj \
|
||||
gyb \
|
||||
clean
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.MapKeyPath.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03/10/2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Returns a publisher that publishes the values of three key paths as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath: The key path of a property on `Output`
|
||||
/// - Returns: A publisher that publishes the value of the key path.
|
||||
public func map<Result>(
|
||||
_ keyPath: KeyPath<Output, Result>
|
||||
) -> Publishers.MapKeyPath<Self, Result> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
keyPath: keyPath
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a publisher that publishes the values of three key paths as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`
|
||||
/// - keyPath1: The key path of another property on `Output`
|
||||
/// - Returns: A publisher that publishes the values of two key paths as a tuple.
|
||||
public func map<Result0, Result1>(
|
||||
_ keyPath0: KeyPath<Output, Result0>,
|
||||
_ keyPath1: KeyPath<Output, Result1>
|
||||
) -> Publishers.MapKeyPath2<Self, Result0, Result1> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
keyPath0: keyPath0,
|
||||
keyPath1: keyPath1
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a publisher that publishes the values of three key paths as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`
|
||||
/// - keyPath1: The key path of another property on `Output`
|
||||
/// - keyPath2: The key path of a third property on `Output`
|
||||
/// - Returns: A publisher that publishes the values of three key paths as a tuple.
|
||||
public func map<Result0, Result1, Result2>(
|
||||
_ keyPath0: KeyPath<Output, Result0>,
|
||||
_ keyPath1: KeyPath<Output, Result1>,
|
||||
_ keyPath2: KeyPath<Output, Result2>
|
||||
) -> Publishers.MapKeyPath3<Self, Result0, Result1, Result2> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
keyPath0: keyPath0,
|
||||
keyPath1: keyPath1,
|
||||
keyPath2: keyPath2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the value of a key path.
|
||||
public struct MapKeyPath<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath: KeyPath<Upstream.Output, Output>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that publishes the values of two key paths as a tuple.
|
||||
public struct MapKeyPath2<Upstream: Publisher, Output0, Output1>: Publisher {
|
||||
|
||||
public typealias Output = (Output0, Output1)
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
/// The key path of a second property to publish.
|
||||
public let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that publishes the values of three key paths as a tuple.
|
||||
public struct MapKeyPath3<Upstream: Publisher, Output0, Output1, Output2>: Publisher {
|
||||
|
||||
public typealias Output = (Output0, Output1, Output2)
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
/// The key path of a second property to publish.
|
||||
public let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
/// The key path of a third property to publish.
|
||||
public let keyPath2: KeyPath<Upstream.Output, Output2>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapKeyPath {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let keyPath: KeyPath<Upstream.Output, Output>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.MapKeyPath<Upstream, Output>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.keyPath = parent.keyPath
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let output = (
|
||||
input[keyPath: keyPath]
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "ValueForKey" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapKeyPath2 {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
private let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.MapKeyPath2<Upstream, Output0, Output1>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.keyPath0 = parent.keyPath0
|
||||
self.keyPath1 = parent.keyPath1
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let output = (
|
||||
input[keyPath: keyPath0],
|
||||
input[keyPath: keyPath1]
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "ValueForKeys" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapKeyPath3 {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
private let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
private let keyPath2: KeyPath<Upstream.Output, Output2>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.MapKeyPath3<Upstream, Output0, Output1, Output2>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.keyPath0 = parent.keyPath0
|
||||
self.keyPath1 = parent.keyPath1
|
||||
self.keyPath2 = parent.keyPath2
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let output = (
|
||||
input[keyPath: keyPath0],
|
||||
input[keyPath: keyPath1],
|
||||
input[keyPath: keyPath2]
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "ValueForKeys" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publishers.MapKeyPath.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03/10/2019.
|
||||
//
|
||||
|
||||
%{
|
||||
instantiations = [(1, '', ''),
|
||||
(2, 'two', 'second '),
|
||||
(3, 'three', 'third ')]
|
||||
|
||||
def suffixed(name, index, arity):
|
||||
return name + ('' if arity == 1 else str(index))
|
||||
|
||||
def key_path_var(index, arity):
|
||||
return suffixed('keyPath', index, arity)
|
||||
|
||||
def type_name(arity):
|
||||
return suffixed('MapKeyPath', arity, arity)
|
||||
|
||||
def make_output_types(arity):
|
||||
return [suffixed('Output', i, arity) for i in range(arity)]
|
||||
}%
|
||||
extension Publisher {
|
||||
% for arity, cardinal, _ in instantiations:
|
||||
% result_types = [suffixed('Result', i, arity) for i in range(arity)]
|
||||
% comma_separated_result_types = ', '.join(result_types)
|
||||
%
|
||||
% method_args = \
|
||||
% ['_ {}: KeyPath<Output, {}>'.format(key_path_var(i, arity), result_types[i]) \
|
||||
% for i in range(arity)]
|
||||
% method_args_joined = ',\n '.join(method_args)
|
||||
%
|
||||
% init_args = ['{}: {}'.format(key_path_var(i, arity), key_path_var(i, arity)) \
|
||||
% for i in range(arity)]
|
||||
% init_args_joined = ',\n '.join(init_args)
|
||||
|
||||
/// Returns a publisher that publishes the values of three key paths as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
% for i in range(arity):
|
||||
% ordinal = 'another ' if i == 1 else 'a ' + instantiations[i][2]
|
||||
/// - ${key_path_var(i, arity)}: The key path of ${ordinal}property on `Output`
|
||||
% end
|
||||
%
|
||||
% doc_comment_suffix = 'value of the key path' \
|
||||
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
|
||||
/// - Returns: A publisher that publishes the ${doc_comment_suffix}.
|
||||
public func map<${comma_separated_result_types}>(
|
||||
${method_args_joined}
|
||||
) -> Publishers.${type_name(arity)}<Self, ${comma_separated_result_types}> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
${init_args_joined}
|
||||
)
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
% for arity, cardinal, ordinal in instantiations:
|
||||
%
|
||||
% doc_comment_suffix = 'value of a key path' \
|
||||
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
|
||||
%
|
||||
% output_types = make_output_types(arity)
|
||||
% comma_separated_output_types = ', '.join(output_types)
|
||||
|
||||
/// A publisher that publishes the ${doc_comment_suffix}.
|
||||
public struct ${type_name(arity)}<Upstream: Publisher, ${comma_separated_output_types}>: Publisher {
|
||||
|
||||
% if arity != 1:
|
||||
public typealias Output = (${comma_separated_output_types})
|
||||
|
||||
% end
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
% for i in range(arity):
|
||||
% ordinal = instantiations[i][2]
|
||||
/// The key path of a ${ordinal}property to publish.
|
||||
public let ${key_path_var(i, arity)}: KeyPath<Upstream.Output, ${output_types[i]}>
|
||||
|
||||
% end
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
% end
|
||||
}
|
||||
% for arity, _, _ in instantiations:
|
||||
% output_types = make_output_types(arity)
|
||||
% comma_separated_output_types = ', '.join(output_types)
|
||||
|
||||
extension Publishers.${type_name(arity)} {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
% for i in range(arity):
|
||||
private let ${key_path_var(i, arity)}: KeyPath<Upstream.Output, ${output_types[i]}>
|
||||
|
||||
% end
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.${type_name(arity)}<Upstream, ${comma_separated_output_types}>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
% for i in range(arity):
|
||||
self.${key_path_var(i, arity)} = parent.${key_path_var(i, arity)}
|
||||
% end
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
% output_components = \
|
||||
% ['input[keyPath: {}]'.format(key_path_var(i, arity)) for i in range(arity)]
|
||||
% output_components_joined = ',\n '.join(output_components)
|
||||
let output = (
|
||||
${output_components_joined}
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
% inner_description = 'ValueForKey' + ('' if arity == 1 else 's')
|
||||
var description: String { return "${inner_description}" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
% end
|
||||
Executable
+1267
File diff suppressed because it is too large
Load Diff
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
find . -name '*.gyb' | \
|
||||
while read file; do \
|
||||
./utils/gyb.py \
|
||||
-Dtemplate_header="$(< utils/template_header.txt)" \
|
||||
--line-directive '' \
|
||||
-o "`dirname ${file%.gyb}`/GENERATED-`basename ${file%.gyb}`" \
|
||||
"$file"; \
|
||||
done
|
||||
@@ -0,0 +1,6 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
Reference in New Issue
Block a user