Skip to content

Commit

Permalink
import casepaths
Browse files Browse the repository at this point in the history
  • Loading branch information
ndurell committed Nov 5, 2024
1 parent 1a33d23 commit 142e040
Show file tree
Hide file tree
Showing 26 changed files with 2,062 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

# format options

--exclude Sources/ConcurrencyExtras,Sources/IssueReporting,Sources/Perception,Sources/IdentifiedCollections,Sources/InternalCollectionsUtilities,Sources/OrderedCollections,Sources/ComposableArchitecture,Sources/KlaviyoSwift/Vendor,Tests/KlaviyoSwiftTests/Vendor,Tests/KlaviyoSwiftTests/__Snapshots__
--exclude Sources/CasePaths,Sources/ConcurrencyExtras,Sources/IssueReporting,Sources/Perception,Sources/IdentifiedCollections,Sources/InternalCollectionsUtilities,Sources/OrderedCollections,Sources/ComposableArchitecture,Sources/KlaviyoSwift/Vendor,Tests/KlaviyoSwiftTests/Vendor,Tests/KlaviyoSwiftTests/__Snapshots__
--closingparen same-line
--commas inline
--comments indent
Expand Down
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ excluded: # paths to ignore during linting. Takes precedence over `included`.
- Sources/Perception
- Sources/IssueReporting
- Sources/ConcurrencyExtras
- Sources/CasePaths
analyzer_rules: # Rules run by `swiftlint analyze` (experimental)
- explicit_self

Expand Down
11 changes: 1 addition & 10 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "8c8cc3892336bc65f365db717e661c18dd83e787da8adb8ec838990b62c6de14",
"originHash" : "6c5f09d9ec6910fb1633586531ed561971f2fce425c4464d438f3e30b0c36842",
"pins" : [
{
"identity" : "combine-schedulers",
Expand All @@ -10,15 +10,6 @@
"version" : "1.0.2"
}
},
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
"revision" : "bc92c4b27f9a84bfb498cdbfdf35d5a357e9161f",
"version" : "1.5.6"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
Expand Down
17 changes: 9 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.10.0"),
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"),
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.4"),
.package(url: "https://github.com/pointfreeco/combine-schedulers", from: "1.0.2")
],
targets: [
Expand All @@ -34,8 +33,7 @@ let package = Package(
dependencies: [
"KlaviyoCore",
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
.product(name: "CustomDump", package: "swift-custom-dump"),
.product(name: "CasePaths", package: "swift-case-paths")
.product(name: "CustomDump", package: "swift-custom-dump")
]),
.target(
name: "KlaviyoSwift",
Expand All @@ -52,11 +50,11 @@ let package = Package(
"KlaviyoSwift",
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
.product(name: "CustomDump", package: "swift-custom-dump"),
.product(name: "CasePaths", package: "swift-case-paths"),
.product(name: "CombineSchedulers", package: "combine-schedulers"),
"KlaviyoCore",
"ComposableArchitecture",
"KIssueReporting"
"KIssueReporting",
"KCasePaths"
],
exclude: [
"__Snapshots__"
Expand All @@ -81,11 +79,11 @@ let package = Package(
.target(
name: "ComposableArchitecture",
dependencies: [
.product(name: "CasePaths", package: "swift-case-paths"),
.product(name: "CustomDump", package: "swift-custom-dump"),
"IdentifiedCollections",
"KConcurrencyExtras",
"KPerception"
"KPerception",
"KCasePaths"
],
path: "Sources/ComposableArchitecture"),
.target(
Expand Down Expand Up @@ -114,6 +112,9 @@ let package = Package(
.target(
name: "KIssueReporting",
dependencies: [],
path: "Sources/IssueReporting")
path: "Sources/IssueReporting"),
.target(name: "KCasePaths",
dependencies: ["KIssueReporting"],
path: "Sources/CasePaths")

])
14 changes: 7 additions & 7 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.10.0"),
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"),
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.4"),
.package(url: "https://github.com/pointfreeco/combine-schedulers", from: "1.0.2")
],
targets: [
Expand All @@ -33,8 +32,7 @@ let package = Package(
dependencies: [
"KlaviyoCore",
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
.product(name: "CustomDump", package: "swift-custom-dump"),
.product(name: "CasePaths", package: "swift-case-paths")
.product(name: "CustomDump", package: "swift-custom-dump")
]),
.target(
name: "KlaviyoSwift",
Expand All @@ -51,7 +49,6 @@ let package = Package(
"KlaviyoSwift",
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
.product(name: "CustomDump", package: "swift-custom-dump"),
.product(name: "CasePaths", package: "swift-case-paths"),
.product(name: "CombineSchedulers", package: "combine-schedulers"),
"KlaviyoCore",
"ComposableArchitecture",
Expand Down Expand Up @@ -80,11 +77,11 @@ let package = Package(
.target(
name: "ComposableArchitecture",
dependencies: [
.product(name: "CasePaths", package: "swift-case-paths"),
.product(name: "CustomDump", package: "swift-custom-dump"),
"IdentifiedCollections",
"KConcurrencyExtras",
"KPerception"
"KPerception",
"KCasePaths"
],
path: "Sources/ComposableArchitecture"),
.target(
Expand Down Expand Up @@ -113,6 +110,9 @@ let package = Package(
.target(
name: "KIssueReporting",
dependencies: [],
path: "Sources/IssueReporting")
path: "Sources/IssueReporting"),
.target(name: "KCasePaths",
dependencies: ["KIssueReporting"],
path: "Sources/CasePaths")
],
swiftLanguageModes: [.v6])
115 changes: 115 additions & 0 deletions Sources/CasePaths/AnyCasePath.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import Foundation

/// A type-erased case path that supports embedding a value in a root and attempting to extract a
/// root's embedded value.
///
/// This type defines key path-like semantics for enum cases, and is used to derive ``CaseKeyPath``s
/// from types that conform to ``CasePathable``.
@dynamicMemberLookup
public struct AnyCasePath<Root, Value>: Sendable {
private let _embed: @Sendable (Value) -> Root
private let _extract: @Sendable (Root) -> Value?

/// Creates a type-erased case path from a pair of functions.
///
/// - Parameters:
/// - embed: A function that always succeeds in embedding a value in a root.
/// - extract: A function that can optionally fail in extracting a value from a root.
public init(
embed: @escaping @Sendable (Value) -> Root,
extract: @escaping @Sendable (Root) -> Value?
) {
self._embed = embed
self._extract = extract
}

public static func _$embed(
_ embed: @escaping (Value) -> Root,
extract: @escaping @Sendable (Root) -> Value?
) -> Self {
#if swift(>=5.10)
nonisolated(unsafe) let embed = embed
return Self(embed: { embed($0) }, extract: extract)
#else
@UncheckedSendable var embed = embed
return Self(embed: { [$embed] in $embed.wrappedValue($0) }, extract: extract)
#endif
}

/// Returns a root by embedding a value.
///
/// - Parameter value: A value to embed.
/// - Returns: A root that embeds `value`.
public func embed(_ value: Value) -> Root {
self._embed(value)
}

/// Attempts to extract a value from a root.
///
/// - Parameter root: A root to extract from.
/// - Returns: A value if it can be extracted from the given root, otherwise `nil`.
public func extract(from root: Root) -> Value? {
self._extract(root)
}
}

extension AnyCasePath where Root == Value {
/// The identity case path.
///
/// A case path that:
///
/// * Given a value to embed, returns the given value.
/// * Given a value to extract, returns the given value.
public init() where Root == Value {
self.init(embed: { $0 }, extract: { $0 })
}
}

extension AnyCasePath: CustomDebugStringConvertible {
public var debugDescription: String {
"AnyCasePath<\(typeName(Root.self)), \(typeName(Value.self))>"
}
}

extension AnyCasePath {
@available(
iOS, deprecated: 9999,
message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
)
@available(
macOS, deprecated: 9999,
message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
)
@available(
tvOS, deprecated: 9999,
message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
)
@available(
watchOS, deprecated: 9999,
message: "Use 'CasePathable.modify', or 'extract' and 'embed', instead."
)
public func modify<Result>(
_ root: inout Root,
_ body: (inout Value) throws -> Result
) throws -> Result {
guard var value = self.extract(from: root) else { throw ExtractionFailed() }
let result = try body(&value)
root = self.embed(value)
return result
}

@available(iOS, deprecated: 9999, message: "Chain case key paths together, instead.")
@available(macOS, deprecated: 9999, message: "Chain case key paths together, instead.")
@available(tvOS, deprecated: 9999, message: "Chain case key paths together, instead.")
@available(watchOS, deprecated: 9999, message: "Chain case key paths together, instead.")
public func appending<AppendedValue>(
path: AnyCasePath<Value, AppendedValue>
) -> AnyCasePath<Root, AppendedValue> {
AnyCasePath<Root, AppendedValue>(
embed: { self.embed(path.embed($0)) },
extract: { self.extract(from: $0).flatMap(path.extract) }
)
}
}

struct ExtractionFailed: Error {}
17 changes: 17 additions & 0 deletions Sources/CasePaths/CasePathIterable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// A type that provides a collection of all of its case paths.
///
/// The ``CasePathable()`` macro automatically generates a conformance to this protocol.
///
/// You can iterate over ``CasePathable/allCasePaths`` to get access to each individual case path:
///
/// ```swift
/// @CasePathable enum Field {
/// case title(String)
/// case body(String
/// case isLive
/// }
///
/// Array(Field.allCasePaths) // [\.title, \.body, \.isLive]
/// ```
public protocol CasePathIterable: CasePathable
where AllCasePaths: Sequence, AllCasePaths.Element == PartialCaseKeyPath<Self> {}
27 changes: 27 additions & 0 deletions Sources/CasePaths/CasePathReflectable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// A type that can reflect a case path from a given case.
///
/// The ``CasePathable()`` macro automatically generates a conformance to this protocol on the
/// enum's ``CasePathable/AllCasePaths`` type.
///
/// You can look up an enum's case path by passing it to ``subscript(root:)``:
///
/// ```swift
/// @CasePathable
/// enum Field {
/// case title(String)
/// case body(String)
/// case isLive
/// }
///
/// Field.allCasePaths[.title("Hello, Blob!")] // \.title
/// ```
public protocol CasePathReflectable<Root> {
/// The enum type that can be reflected.
associatedtype Root: CasePathable

/// Returns the case key path for a given root value.
///
/// - Parameter root: An root value.
/// - Returns: A case path to the root value.
subscript(root: Root) -> PartialCaseKeyPath<Root> { get }
}
Loading

0 comments on commit 142e040

Please sign in to comment.