From 4b4c8aa01be61e803890b6ed6a5d1d36dd447d3f Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Thu, 10 Nov 2022 11:18:58 +0100 Subject: [PATCH 1/3] SectionBuilder --- Eureka.xcodeproj/project.pbxproj | 8 ++++ Source/Core/Operators.swift | 30 ++++++++++++ Source/Core/ResultBuilders.swift | 68 ++++++++++++++++++++++++++++ Tests/ResultBuildersTests.swift | 78 ++++++++++++++++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 Source/Core/ResultBuilders.swift create mode 100644 Tests/ResultBuildersTests.swift diff --git a/Eureka.xcodeproj/project.pbxproj b/Eureka.xcodeproj/project.pbxproj index 97e5907ed..544fb151c 100644 --- a/Eureka.xcodeproj/project.pbxproj +++ b/Eureka.xcodeproj/project.pbxproj @@ -88,6 +88,8 @@ 8FCCF8F120A32613004793A0 /* TriplePickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FCCF8F020A32613004793A0 /* TriplePickerRow.swift */; }; 8FCCF92320A473E7004793A0 /* DoublePickerInputRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FCCF92220A473E7004793A0 /* DoublePickerInputRow.swift */; }; 8FCCF92520A4794B004793A0 /* TriplePickerInputRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FCCF92420A4794B004793A0 /* TriplePickerInputRow.swift */; }; + 9EFC727C291D0369004840A9 /* ResultBuildersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EFC727B291D0369004840A9 /* ResultBuildersTests.swift */; }; + 9EFC727F291D05FB004840A9 /* ResultBuilders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EFC727D291D05EA004840A9 /* ResultBuilders.swift */; }; B244E6541FE1C94F0026B944 /* AlertOptionsRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B244E6531FE1C94F0026B944 /* AlertOptionsRow.swift */; }; B257FE2E1EC0F66900043911 /* RowsInsertionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B257FE2D1EC0F66900043911 /* RowsInsertionTests.swift */; }; B2A401161EC0BA140042EDF0 /* SectionsInsertionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A401151EC0BA140042EDF0 /* SectionsInsertionTests.swift */; }; @@ -189,6 +191,8 @@ 8FCCF8F020A32613004793A0 /* TriplePickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriplePickerRow.swift; sourceTree = ""; }; 8FCCF92220A473E7004793A0 /* DoublePickerInputRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoublePickerInputRow.swift; sourceTree = ""; }; 8FCCF92420A4794B004793A0 /* TriplePickerInputRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriplePickerInputRow.swift; sourceTree = ""; }; + 9EFC727B291D0369004840A9 /* ResultBuildersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultBuildersTests.swift; sourceTree = ""; }; + 9EFC727D291D05EA004840A9 /* ResultBuilders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultBuilders.swift; sourceTree = ""; }; B244E6531FE1C94F0026B944 /* AlertOptionsRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AlertOptionsRow.swift; path = Common/AlertOptionsRow.swift; sourceTree = ""; }; B257FE2D1EC0F66900043911 /* RowsInsertionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RowsInsertionTests.swift; path = Tests/RowsInsertionTests.swift; sourceTree = SOURCE_ROOT; }; B2A401151EC0BA140042EDF0 /* SectionsInsertionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SectionsInsertionTests.swift; path = Tests/SectionsInsertionTests.swift; sourceTree = SOURCE_ROOT; }; @@ -228,6 +232,7 @@ 2859CDC51C7D1A2E0002982F /* NavigationAccessoryView.swift */, 28EEBFFA1C7E25C500300699 /* Operators.swift */, 28EEBFEB1C7E224200300699 /* PresenterRowType.swift */, + 9EFC727D291D05EA004840A9 /* ResultBuilders.swift */, 28EEBFEF1C7E23F400300699 /* Row.swift */, 28EEBFED1C7E231C00300699 /* RowControllerType.swift */, 28EEBFF31C7E240000300699 /* RowProtocols.swift */, @@ -370,6 +375,7 @@ 28B1D7761C7F90C900605EB3 /* HelperMethodTests.swift */, 28B1D7771C7F90C900605EB3 /* HiddenRowsTests.swift */, 28B1D7781C7F90C900605EB3 /* OperatorsTest.swift */, + 9EFC727B291D0369004840A9 /* ResultBuildersTests.swift */, 28B1D7791C7F90C900605EB3 /* RowByTagTests.swift */, 28B1D77A1C7F90C900605EB3 /* RowCallbackTests.swift */, 28B1D77B1C7F90C900605EB3 /* SetValuesTests.swift */, @@ -531,6 +537,7 @@ 2859CD961C7CF1FD0002982F /* Helpers.swift in Sources */, 287E7D821D74E64C0065F4DE /* RuleURL.swift in Sources */, 28EEBFF21C7E23FA00300699 /* RowType.swift in Sources */, + 9EFC727F291D05FB004840A9 /* ResultBuilders.swift in Sources */, 287E7D801D74E42D0065F4DE /* RuleRegExp.swift in Sources */, 28EEC0101C7E399500300699 /* DateInlineFieldRow.swift in Sources */, 2859CDC61C7D1A2E0002982F /* NavigationAccessoryView.swift in Sources */, @@ -574,6 +581,7 @@ 28B1D78C1C7F911900605EB3 /* HelperMethodTests.swift in Sources */, 28B1D78B1C7F911900605EB3 /* FormValuesTests.swift in Sources */, B2A401161EC0BA140042EDF0 /* SectionsInsertionTests.swift in Sources */, + 9EFC727C291D0369004840A9 /* ResultBuildersTests.swift in Sources */, 28B1D7871C7F911900605EB3 /* BaseEurekaTests.swift in Sources */, 28B1D7911C7F911900605EB3 /* SetValuesTests.swift in Sources */, 285BA44A1E8187480034EE92 /* MultivaluedSectionTests.swift in Sources */, diff --git a/Source/Core/Operators.swift b/Source/Core/Operators.swift index a98a23d63..47f5d9306 100644 --- a/Source/Core/Operators.swift +++ b/Source/Core/Operators.swift @@ -52,6 +52,21 @@ public func +++ (left: Form, right: Section) -> Form { return left } +/** + Appends a section to a form + + - parameter left: the form + - parameter right: the section to be appended + + - returns: the updated form + */ +#if swift(>=5.4) +@discardableResult +public func +++ (left: Form, @SectionBuilder right: () -> Section) -> Form { + left +++ right() +} +#endif + /** Appends a row to the last section of a form @@ -80,6 +95,21 @@ public func +++ (left: Section, right: Section) -> Form { return form } +/** + Creates a form with two sections + + - parameter left: the first section + - parameter right: the second section + + - returns: the created form + */ +#if swift(>=5.4) +@discardableResult +public func +++ (left: Section, @SectionBuilder right: () -> Section) -> Form { + left +++ right() +} +#endif + /** Appends the row wrapped in a new section diff --git a/Source/Core/ResultBuilders.swift b/Source/Core/ResultBuilders.swift new file mode 100644 index 000000000..8bbdf6dd4 --- /dev/null +++ b/Source/Core/ResultBuilders.swift @@ -0,0 +1,68 @@ +// ResultBuilders.swift +// Eureka ( https://github.com/xmartlabs/Eureka ) +// +// Copyright (c) 2022 Xmartlabs ( http://xmartlabs.com ) +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if swift(>=5.4) +public protocol RowsProvider { + var rows: [BaseRow] { get } +} + +extension BaseRow: RowsProvider { + public var rows: [BaseRow] { [self] } +} + +extension Array: RowsProvider where Element == BaseRow { + public var rows: [BaseRow] { self } +} + +@resultBuilder +public struct SectionBuilder { + public static func buildBlock(_ components: RowsProvider...) -> [BaseRow] { + components.flatMap { $0.rows } + } + + public static func buildFinalResult(_ components: [BaseRow]) -> Section { + .init(components) + } + + public static func buildEither(first components: [RowsProvider]) -> [BaseRow] { + components.flatMap { $0.rows } + } + + public static func buildEither(second components: [RowsProvider]) -> [BaseRow] { + components.flatMap { $0.rows } + } + + public static func buildOptional(_ components: [RowsProvider]?) -> [BaseRow] { + components?.flatMap { $0.rows } ?? [] + } + + public static func buildExpression(_ expression: String?) -> [BaseRow] { + [.init(tag: expression)] + } + + public static func buildExpression(_ expression: BaseRow?) -> [BaseRow] { + expression.flatMap { [$0] } ?? [] + } +} +#endif diff --git a/Tests/ResultBuildersTests.swift b/Tests/ResultBuildersTests.swift new file mode 100644 index 000000000..fe6ff39d8 --- /dev/null +++ b/Tests/ResultBuildersTests.swift @@ -0,0 +1,78 @@ +// ResultBuildersTests.swift +// Eureka ( https://github.com/xmartlabs/Eureka ) +// +// Copyright (c) 2022 Xmartlabs ( http://xmartlabs.com ) +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import XCTest +@testable import Eureka + +class ResultBuildersTests: BaseEurekaTests { + #if swift(>=5.4) + @SectionBuilder + var section1: Section { + "TextRow_f1" + NameRow("NameRow_f1") { $0.title = "Name" } + if true { + IntRow("IntRow_f1") { $0.title = "Int" } + } + DecimalRow("DecimalRow_f1") { $0.title = "Decimal" } + } + #endif + + func testSectionBuilder() { + #if swift(>=5.4) + var checkBuildEither = false + manySectionsForm = (section1 +++ { + URLRow("UrlRow_f1") { $0.title = "Url" } + if checkBuildEither { + TwitterRow("TwitterRow_f2") { $0.title = "Twitter" } + } else { + TwitterRow("TwitterRow_f1") { $0.title = "Twitter" } + } + AccountRow("AccountRow_f1") { $0.title = "Account" } + }) + checkBuildEither = true + manySectionsForm +++ { + "EmailRow_f1" + if checkBuildEither { + PhoneRow("PhoneRow_f1") { $0.title = "Phone" } + } else { + PhoneRow("PhoneRow_f2") { $0.title = "Phone" } + } + PasswordRow("PasswordRow_f1") { $0.title = "Password" } + } + + XCTAssertNotNil(manySectionsForm.rowBy(tag: "TextRow_f1")) + XCTAssertNotNil(manySectionsForm.rowBy(tag: "NameRow_f1")) + XCTAssertNotNil(manySectionsForm.rowBy(tag: "IntRow_f1")) + XCTAssertNotNil(manySectionsForm.rowBy(tag: "DecimalRow_f1")) + XCTAssertNotNil(manySectionsForm.rowBy(tag: "UrlRow_f1")) + XCTAssertNotNil(manySectionsForm.rowBy(tag: "TwitterRow_f1")) + XCTAssertNil(manySectionsForm.rowBy(tag: "TwitterRow_f2")) + XCTAssertNotNil(manySectionsForm.rowBy(tag: "AccountRow_f1")) + XCTAssertNotNil(manySectionsForm.rowBy(tag: "EmailRow_f1")) + XCTAssertNotNil(manySectionsForm.rowBy(tag: "PhoneRow_f1")) + XCTAssertNil(manySectionsForm.rowBy(tag: "PhoneRow_f2")) + XCTAssertNotNil(manySectionsForm.rowBy(tag: "PasswordRow_f1")) + #endif + } +} From 934ae7323d6fdf2f742f279bcfa92bf3ff8a5265 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Tue, 15 Nov 2022 14:13:43 +0100 Subject: [PATCH 2/3] Less code --- Source/Core/ResultBuilders.swift | 4 ---- Tests/ResultBuildersTests.swift | 4 ---- 2 files changed, 8 deletions(-) diff --git a/Source/Core/ResultBuilders.swift b/Source/Core/ResultBuilders.swift index 8bbdf6dd4..43bc1ecd9 100644 --- a/Source/Core/ResultBuilders.swift +++ b/Source/Core/ResultBuilders.swift @@ -57,10 +57,6 @@ public struct SectionBuilder { components?.flatMap { $0.rows } ?? [] } - public static func buildExpression(_ expression: String?) -> [BaseRow] { - [.init(tag: expression)] - } - public static func buildExpression(_ expression: BaseRow?) -> [BaseRow] { expression.flatMap { [$0] } ?? [] } diff --git a/Tests/ResultBuildersTests.swift b/Tests/ResultBuildersTests.swift index fe6ff39d8..aa0691842 100644 --- a/Tests/ResultBuildersTests.swift +++ b/Tests/ResultBuildersTests.swift @@ -29,7 +29,6 @@ class ResultBuildersTests: BaseEurekaTests { #if swift(>=5.4) @SectionBuilder var section1: Section { - "TextRow_f1" NameRow("NameRow_f1") { $0.title = "Name" } if true { IntRow("IntRow_f1") { $0.title = "Int" } @@ -52,7 +51,6 @@ class ResultBuildersTests: BaseEurekaTests { }) checkBuildEither = true manySectionsForm +++ { - "EmailRow_f1" if checkBuildEither { PhoneRow("PhoneRow_f1") { $0.title = "Phone" } } else { @@ -61,7 +59,6 @@ class ResultBuildersTests: BaseEurekaTests { PasswordRow("PasswordRow_f1") { $0.title = "Password" } } - XCTAssertNotNil(manySectionsForm.rowBy(tag: "TextRow_f1")) XCTAssertNotNil(manySectionsForm.rowBy(tag: "NameRow_f1")) XCTAssertNotNil(manySectionsForm.rowBy(tag: "IntRow_f1")) XCTAssertNotNil(manySectionsForm.rowBy(tag: "DecimalRow_f1")) @@ -69,7 +66,6 @@ class ResultBuildersTests: BaseEurekaTests { XCTAssertNotNil(manySectionsForm.rowBy(tag: "TwitterRow_f1")) XCTAssertNil(manySectionsForm.rowBy(tag: "TwitterRow_f2")) XCTAssertNotNil(manySectionsForm.rowBy(tag: "AccountRow_f1")) - XCTAssertNotNil(manySectionsForm.rowBy(tag: "EmailRow_f1")) XCTAssertNotNil(manySectionsForm.rowBy(tag: "PhoneRow_f1")) XCTAssertNil(manySectionsForm.rowBy(tag: "PhoneRow_f2")) XCTAssertNotNil(manySectionsForm.rowBy(tag: "PasswordRow_f1")) From 26df8c3e204c1f145ec6e611d8a193dbf539f668 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Tue, 15 Nov 2022 14:39:53 +0100 Subject: [PATCH 3/3] Documentation --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 69289f1a5..7543fcfa0 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,34 @@ form += [Section("A"), Section("B"), Section("C")] section += [TextRow(), DateRow()] ``` +### Result builders + +Eureka includes result builders to make form creation easy: + +#### @SectionBuilder +```swift +// Section + Section +form = (Section("A") +++ { + URLRow("UrlRow_f1") { $0.title = "Url" } + if something { + TwitterRow("TwitterRow_f2") { $0.title = "Twitter" } + } else { + TwitterRow("TwitterRow_f1") { $0.title = "Twitter" } + } + AccountRow("AccountRow_f1") { $0.title = "Account" } +}) + +// Form + Section +form +++ { + if something { + PhoneRow("PhoneRow_f1") { $0.title = "Phone" } + } else { + PhoneRow("PhoneRow_f2") { $0.title = "Phone" } + } + PasswordRow("PasswordRow_f1") { $0.title = "Password" } +} +``` + ### Using the callbacks Eureka includes callbacks to change the appearance and behavior of a row.