-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d35fd49
Showing
36 changed files
with
2,556 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# This workflow will build a Swift project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift | ||
|
||
name: Build and Test | ||
on: | ||
push: | ||
branches: [ "main" ] | ||
pull_request: | ||
branches: [ "main" ] | ||
|
||
jobs: | ||
build-and-test: | ||
runs-on: macos-latest | ||
steps: | ||
- uses: swift-actions/setup-swift@v2 | ||
with: | ||
swift-version: "5.10.0" | ||
- name: Check out code | ||
uses: actions/checkout@v2 | ||
- name: Build | ||
run: swift build -v | ||
- name: Test | ||
run: swift test -v |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.DS_Store | ||
/.build | ||
/.swiftpm | ||
/Packages | ||
/*.swiftinterface | ||
/*.xcodeproj | ||
xcuserdata/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# file options | ||
|
||
--swiftversion 5.9 | ||
--exclude .build, **/*/Diagnostics.swift, **/*/DiagnosticsType.swift | ||
|
||
# format options | ||
--voidtype void | ||
--ifdef no-indent | ||
--indent 4 | ||
--importgrouping testable-bottom | ||
--maxwidth 100 | ||
--stripunusedargs always | ||
--trimwhitespace always | ||
--wraparguments before-first | ||
--wrapcollections before-first | ||
--wrapconditions after-first | ||
--typeattributes prev-line | ||
--funcattributes prev-line | ||
--varattributes prev-line | ||
--lineaftermarks true | ||
--typeblanklines remove | ||
--extensionacl on-declarations | ||
--asynccapturing explicit | ||
--throwcapturing explicit | ||
--guardelse same-line | ||
--elseposition same-line | ||
--header \n {file}\n\n Created by Ahmed Ali (github.com/Ahmed-Ali) on {created}.\n | ||
|
||
## Rules | ||
--enable preferKeyPath, leadingDelimiters, linebreakAtEndOfFile, conditionalAssignment, consecutiveBlankLines, redundantReturn, redundantObjc, redundantSelf, redundantVoidReturnType, redundantFileprivate, redundantType, redundantExtensionACL, redundantGet, redundantInit, redundantLet, redundantPattern, redundantRawValues, duplicateImports, emptyBraces, spaceAroundParens, spaceInsideBraces, spaceInsideBrackets, strongifiedSelf, todos, trailingClosures, wrapSingleLineComments, wrapSwitchCases, yodaConditions | ||
|
||
--disable trailingCommas, andOperator, wrapMultilineStatementBraces |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
disabled_rules: | ||
- line_length # Handled by swiftformat | ||
- identifier_name # Not great for for loops | ||
|
||
excluded: # paths or files to ignore during linting. Takes precedence over `included`. | ||
- .build | ||
- Package.swift | ||
- Tests | ||
|
||
strict: true # If true, SwiftLint will treat all warnings as errors. | ||
|
||
force_exclusion: true # if true, will fail if there are SwiftLint violations in the excluded files | ||
|
||
reporter: "emoji" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 Ahmed Ali | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"originHash" : "f50bfa61790990bf4bfe36a55c7c976fd799f9af19aecf85a907235440d59514", | ||
"pins" : [ | ||
{ | ||
"identity" : "lefthook-plugin", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/csjones/lefthook-plugin.git", | ||
"state" : { | ||
"revision" : "348e8fccfa863b3805c21e25aa2a6f08cd45d94a", | ||
"version" : "1.6.10" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-syntax", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-syntax.git", | ||
"state" : { | ||
"revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd", | ||
"version" : "510.0.1" | ||
} | ||
} | ||
], | ||
"version" : 3 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// swift-tools-version: 5.10 | ||
// The swift-tools-version declares the minimum version of Swift required to | ||
// build this package. | ||
|
||
import CompilerPluginSupport | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "BuildDSL", | ||
platforms: [ | ||
.macOS(.v11), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), | ||
.macCatalyst(.v13), .visionOS(.v1) | ||
], | ||
products: [ | ||
.library( | ||
name: "BuildDSL", | ||
targets: ["BuildDSL"] | ||
), | ||
.executable( | ||
name: "BuildDSLClient", | ||
targets: ["BuildDSLClient"] | ||
) | ||
], | ||
dependencies: [ | ||
.package( | ||
url: "https://github.com/apple/swift-syntax.git", | ||
from: "510.0.1" | ||
), | ||
.package(url: "https://github.com/csjones/lefthook-plugin.git", from: "1.6.10") | ||
], | ||
targets: [ | ||
.macro( | ||
name: "BuildDSLMacros", | ||
dependencies: [ | ||
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"), | ||
.product(name: "SwiftCompilerPlugin", package: "swift-syntax") | ||
] | ||
), | ||
.target( | ||
name: "BuildDSL", | ||
dependencies: ["BuildDSLMacros"] | ||
), | ||
.executableTarget( | ||
name: "BuildDSLClient", | ||
dependencies: ["BuildDSL"] | ||
), | ||
.testTarget( | ||
name: "BuildDSLTests", | ||
dependencies: [ | ||
"BuildDSL", | ||
"BuildDSLMacros", | ||
.product( | ||
name: "SwiftSyntaxMacrosTestSupport", | ||
package: "swift-syntax" | ||
) | ||
] | ||
) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# BuildDSL | ||
|
||
BuildDSL is a Swift package that offers a robust Domain-Specific Language (DSL) for crafting intuitive builder APIs for Swift structs. It streamlines the creation of complex objects with a clean, type-safe syntax, utilizing Swift's `@resultBuilder`, protocols, and generics, along with an auto-generated Builder pattern. | ||
|
||
## Features | ||
|
||
- **Type-Safe Builder Pattern**: Auto-generate builders for Swift structs with compile-time type checks. | ||
- **Declarative Syntax**: Employ a succinct DSL to outline your object construction. | ||
- **Automatic Code Generation**: Minimize boilerplate with auto-generated builder code. | ||
- **Nested Builders**: Seamlessly construct complex objects using nested builders. | ||
- **Error Handling**: Utilize `Result` types for comprehensive error handling. | ||
- **Customizable Defaults**: Specify default values for immutable fields with `@Default`. | ||
- **Property Exclusion**: Omit properties from the builder with `@Ignore`. | ||
|
||
## Installation | ||
|
||
### Swift Package Manager | ||
|
||
Add BuildDSL to your project with Swift Package Manager by including the following in your `Package.swift`: | ||
|
||
```swift | ||
dependencies: [ | ||
.package(url: "https://github.com/Ahmed-Ali/BuildDSL.git", from:"0.1.0") | ||
] | ||
``` | ||
|
||
## Usage | ||
|
||
Annotate your struct with `@Builder` and use `@Default`, `@Ignore`, and `@Escaping` for struct fields. Here's an example: | ||
|
||
```swift | ||
import BuildDSL | ||
|
||
@Builder | ||
struct Post { | ||
let title: String | ||
let content: String | ||
@Default(Date()) | ||
let createdOn: Date | ||
@Ignore | ||
var popularityScore: Int = 5 | ||
} | ||
|
||
// Create a Post using the generated Builder | ||
let post = Post { $0 | ||
.title("BuildDSL") | ||
.content("Building Intuitive APIs with BuildDSL") | ||
} | ||
|
||
// Handle errors with try-catch | ||
do { | ||
let mustHavePost = try Post.build { $0 | ||
// ... | ||
}.get() | ||
} catch { | ||
// Error handling | ||
} | ||
|
||
// Or use a switch statement | ||
let result = Post.build { $0.title("Title") } | ||
switch result { | ||
case .success(let post): | ||
// Required fields are set | ||
case .failure(let error): | ||
// Inspect error.container and error.property | ||
} | ||
``` | ||
|
||
For more examples, see [Sources/BuildDSLClient/main.swift](Sources/BuildDSLClient/main.swift) and [Tests/BuildDSLTests/MacroUsageTests.swift](Tests/BuildDSLTests/MacroUsageTests.swift). | ||
The [Sources/BuildDSL/Macros.swift](Sources/BuildDSL/Macros.swift) also documents each macro in details | ||
|
||
## FAQ | ||
- **Why use `@resultBuilder` instead of a closure?** | ||
`@resultBuilder` ensures the closure is solely used for object construction, preventing arbitrary code execution and potential misuse. | ||
|
||
- **Are there benefits to using Builder pattern + `@resultBuilder` over just a Builder?** | ||
Yes, it enhances discoverability and IDE assistance, making it easier to understand how to initialize objects with complex configurations. | ||
|
||
## Known Limitations & Workarounds | ||
- **Initialization**: Structs must have a memberwise initializer. Exclude properties with `@Ignore` and provide default values or implement the initializer yourself. | ||
- **Buildable Dependencies**: Declare dependent structs before dependees to ensure proper macro execution and avoid compilation errors. | ||
- **Autocomplete**: While macros and Swift's Generics with `@resultBuilder` are powerful, IDE autocomplete may be less helpful with complex nested types. Patience and manual code entry may be required at times. | ||
|
||
## Contribution | ||
PRs and issues are always welcome. I will create a more comprehensive cotribution guidance if needed. | ||
|
||
Happy building with BuildDSL! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// | ||
// BuilderError.swift | ||
// | ||
// Created by Ahmed Ali (github.com/Ahmed-Ali) on 22/04/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
/** | ||
Every generated Builder will return Result.failure(BuilderError) | ||
if a non-optional field without a default value hasn't been set | ||
*/ | ||
public enum BuilderError: Swift.Error { | ||
case missingValueFor(_ property: String, container: String) | ||
|
||
public var property: String? { | ||
switch self { | ||
case let .missingValueFor(property, container: _): | ||
return property | ||
} | ||
} | ||
|
||
public var container: String? { | ||
switch self { | ||
case let .missingValueFor(_, container: container): | ||
return container | ||
} | ||
} | ||
} | ||
|
||
extension BuilderError: CustomStringConvertible { | ||
public var description: String { | ||
switch self { | ||
case let .missingValueFor(property, container: container): | ||
return "Missing a non-optional value for \(container).\(property)" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// | ||
// DSLResultBuilder.swift | ||
// | ||
// Created by Ahmed Ali (github.com/Ahmed-Ali) on 22/04/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
/** | ||
This result builder is one of the pieces that helps ensure the safety of the builder closures. | ||
Without it, the user of the builder closure can excute any code in the closure. | ||
Using result builder, ensure only the builder APIs are usable within that closure | ||
*/ | ||
|
||
@resultBuilder | ||
public struct DSLResultBuilder<Builder: BuilderAPI> { | ||
public static func buildExpression(_ instance: Builder) -> Builder { | ||
instance | ||
} | ||
|
||
public static func buildEither(first instance: Builder) -> Builder { | ||
instance | ||
} | ||
|
||
public static func buildEither(second instance: Builder) -> Builder { | ||
instance | ||
} | ||
|
||
public static func buildBlock(_ builder: Builder) -> Builder { | ||
builder | ||
} | ||
|
||
public static func buildFinalResult(_ builder: Builder) -> Builder.Result { | ||
builder.build() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// | ||
// Protocols.swift | ||
// | ||
// Created by Ahmed Ali (github.com/Ahmed-Ali) on 22/04/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
public protocol BuildableAPI<Builder> { | ||
associatedtype Builder: BuilderAPI where Builder.Buildable == Self | ||
|
||
typealias ResultBuilder = DSLResultBuilder<Builder> | ||
typealias Result = Swift.Result<Self, BuilderError> | ||
typealias Closure = (Builder) -> Self.Builder.Result | ||
} | ||
|
||
public protocol BuilderAPI<Buildable> { | ||
associatedtype Buildable: BuildableAPI | ||
typealias Result = Buildable.Result | ||
|
||
init() | ||
|
||
func build() -> Result | ||
} | ||
|
||
extension BuildableAPI { | ||
public static func build( | ||
@ResultBuilder _ resBuilder: Self | ||
.Closure | ||
) -> Self.Result { | ||
resBuilder(Self.Builder()) | ||
} | ||
} |
Oops, something went wrong.