Skip to content

Commit

Permalink
Merge pull request #6 from MFB-Technologies-Inc/feature/improve-optio…
Browse files Browse the repository at this point in the history
…n-decodable-conformance

Feature/improve option decodable conformance
  • Loading branch information
roanutil authored May 9, 2023
2 parents 88d8003 + f734f04 commit 7cff84a
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 14 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
run: xcrun llvm-cov export -format="lcov" .build/debug/swift-argument-encodingPackageTests.xctest/Contents/MacOS/swift-argument-encodingPackageTests -instr-profile .build/debug/codecov/default.profdata > coverage_report.lcov
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true # optional (default = false)
linux-test:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Pods/
.DS_Store
.AppleDouble
.LSOverride
/.default.profraw

# Icon must end with two \r
Icon
Expand Down
12 changes: 6 additions & 6 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
"revision" : "882ac01eb7ef9e36d4467eb4b1151e74fcef85ab",
"version" : "0.9.1"
"revision" : "0625932976b3ae23949f6b816d13bd97f3b40b7c",
"version" : "0.10.0"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "20b25ca0dd88ebfb9111ec937814ddc5a8880172",
"version" : "0.2.0"
"revision" : "f9acfa1a45f4483fe0f2c434a74e6f68f865d12d",
"version" : "0.3.0"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies.git",
"state" : {
"revision" : "98650d886ec950b587d671261f06d6b59dec4052",
"version" : "0.4.1"
"revision" : "ad0a6a0dd4d4741263e798f4f5029589c9b5da73",
"version" : "0.4.2"
}
},
{
Expand Down
129 changes: 121 additions & 8 deletions Sources/ArgumentEncoding/Option.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// Copyright © 2023 MFB Technologies, Inc. All rights reserved.

import Dependencies
import Foundation

/// A key/value pair argument that provides a given value for a option or variable.
///
Expand Down Expand Up @@ -112,7 +113,7 @@ extension Option where Value: CustomStringConvertible {
public init(key: some CustomStringConvertible, value: Value) {
keyOverride = key.description
wrappedValue = value
unwrap = { [$0.description] }
unwrap = Self.unwrap(_:)
}

/// Initializes a new option when used as a `@propertyWrapper`
Expand All @@ -123,7 +124,72 @@ extension Option where Value: CustomStringConvertible {
public init(wrappedValue: Value, _ key: String? = nil) {
keyOverride = key
self.wrappedValue = wrappedValue
unwrap = { [$0.description] }
unwrap = Self.unwrap(_:)
}

@Sendable
public static func unwrap(_ value: Value) -> [String] {
[value.description]
}
}

extension Option where Value: RawRepresentable, Value.RawValue: CustomStringConvertible {
/// Initializes a new option when not used as a `@propertyWrapper`
///
/// - Parameters
/// - key: Explicit key value
/// - wrappedValue: The underlying value
public init(key: some CustomStringConvertible, value: Value) {
keyOverride = key.description
wrappedValue = value
unwrap = Self.unwrap(_:)
}

/// Initializes a new option when used as a `@propertyWrapper`
///
/// - Parameters
/// - wrappedValue: The underlying value
/// - _ key: Optional explicit key value
public init(wrappedValue: Value, _ key: String? = nil) {
keyOverride = key
self.wrappedValue = wrappedValue
unwrap = Self.unwrap(_:)
}

@Sendable
public static func unwrap(_ value: Value) -> [String] {
[value.rawValue.description]
}
}

extension Option where Value: CustomStringConvertible, Value: RawRepresentable,
Value.RawValue: CustomStringConvertible
{
/// Initializes a new option when not used as a `@propertyWrapper`
///
/// - Parameters
/// - key: Explicit key value
/// - wrappedValue: The underlying value
public init(key: some CustomStringConvertible, value: Value) {
keyOverride = key.description
wrappedValue = value
unwrap = Self.unwrap(_:)
}

/// Initializes a new option when used as a `@propertyWrapper`
///
/// - Parameters
/// - wrappedValue: The underlying value
/// - _ key: Optional explicit key value
public init(wrappedValue: Value, _ key: String? = nil) {
keyOverride = key
self.wrappedValue = wrappedValue
unwrap = Self.unwrap(_:)
}

@Sendable
public static func unwrap(_ value: Value) -> [String] {
[value.rawValue.description]
}
}

Expand All @@ -140,7 +206,7 @@ extension Option {
{
keyOverride = key.description
wrappedValue = value
unwrap = { [$0?.description].compactMap { $0 } }
unwrap = Self.unwrap(_:)
}

/// Initializes a new option when used as a `@propertyWrapper`
Expand All @@ -153,7 +219,14 @@ extension Option {
{
keyOverride = key
self.wrappedValue = wrappedValue
unwrap = { [$0?.description].compactMap { $0 } }
unwrap = Self.unwrap(_:)
}

@Sendable
public static func unwrap<Wrapped>(_ value: Wrapped?) -> [String] where Wrapped: CustomStringConvertible,
Value == Wrapped?
{
[value?.description].compactMap { $0 }
}
}

Expand All @@ -170,7 +243,7 @@ extension Option {
{
keyOverride = key.description
wrappedValue = values
unwrap = { $0.map(\E.description) }
unwrap = Self.unwrap(_:)
}

/// Initializes a new option when used as a `@propertyWrapper`
Expand All @@ -183,7 +256,14 @@ extension Option {
{
keyOverride = key
self.wrappedValue = wrappedValue
unwrap = { $0.map(\E.description) }
unwrap = Self.unwrap(_:)
}

@Sendable
public static func unwrap<E>(_ value: Value) -> [String] where Value: Sequence, Value.Element == E,
E: CustomStringConvertible
{
value.map(\E.description)
}
}

Expand Down Expand Up @@ -227,10 +307,43 @@ extension Option: ExpressibleByStringInterpolation where Value: StringProtocol {
}
}

extension Option: Decodable where Value: Decodable & CustomStringConvertible {
extension Option: DecodableWithConfiguration where Value: Decodable {
public init(from decoder: Decoder, configuration: @escaping @Sendable (Value) -> [String]) throws {
let container = try decoder.singleValueContainer()
try self.init(wrappedValue: container.decode(Value.self), nil, configuration)
}
}

extension Option: Decodable where Value: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
try self.init(wrappedValue: container.decode(Value.self))
guard let configurationCodingUserInfoKey = Self.configurationCodingUserInfoKey(for: Value.Type.self) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "No CodingUserInfoKey found for accessing the DecodingConfiguration.",
underlyingError: nil
))
}
guard let _configuration = decoder.userInfo[configurationCodingUserInfoKey] else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "No DecodingConfiguration found for key: \(configurationCodingUserInfoKey.rawValue)",
underlyingError: nil
))
}
guard let configuration = _configuration as? Self.DecodingConfiguration else {
let desc = "Invalid DecodingConfiguration found for key: \(configurationCodingUserInfoKey.rawValue)"
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: desc,
underlyingError: nil
))
}
try self.init(wrappedValue: container.decode(Value.self), nil, configuration)
}

public static func configurationCodingUserInfoKey(for _: (some Any).Type) -> CodingUserInfoKey? {
CodingUserInfoKey(rawValue: ObjectIdentifier(Self.self).debugDescription)
}
}

Expand Down
36 changes: 36 additions & 0 deletions Tests/ArgumentEncodingTests/OptionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,40 @@ final class OptionTests: XCTestCase {
}
XCTAssertEqual(args, ["--configuration", "release"])
}

func testBothRawValueAndStringConvertible() throws {
let option = Option(key: "configuration", value: RawValueCustomStringConvertible(rawValue: "release"))
let args = withDependencies { values in
values.optionFormatter = .doubleDashPrefix
} operation: {
option.arguments()
}
XCTAssertEqual(args, ["--configuration", "release"])
}

func testBothRawValueAndStringConvertibleContainer() throws {
let container = Container(configuration: RawValueCustomStringConvertible(rawValue: "release"))
let args = withDependencies { values in
values.optionFormatter = .doubleDashPrefix
} operation: {
container.arguments()
}
XCTAssertEqual(args, ["--configuration", "release"])
}
}

private struct RawValueCustomStringConvertible: RawRepresentable, CustomStringConvertible {
var rawValue: String

var description: String {
"description=" + rawValue
}
}

private struct Container: ArgumentGroup {
@Option var configuration: RawValueCustomStringConvertible

init(configuration: RawValueCustomStringConvertible) {
_configuration = Option(wrappedValue: configuration)
}
}

0 comments on commit 7cff84a

Please sign in to comment.