Use gyb tool to implement MapKeyPath

This commit is contained in:
Sergej Jaskiewicz
2019-10-03 20:52:34 +03:00
committed by Sergej Jaskiewicz
parent d7b9e87f6d
commit 14b7ced2fe
7 changed files with 1815 additions and 0 deletions
+64
View File
@@ -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,
+4
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ┃
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
// ┃ ┃
// ┃ ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛