Skip to content

Commit

Permalink
Merge pull request #7 from MFB-Technologies-Inc/feature/add-positiona…
Browse files Browse the repository at this point in the history
…l-and-option-set

Feature/add positional and option set
  • Loading branch information
roanutil authored May 20, 2023
2 parents 7cff84a + 4a27513 commit faa43b0
Show file tree
Hide file tree
Showing 22 changed files with 1,121 additions and 325 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies.git",
"state" : {
"revision" : "ad0a6a0dd4d4741263e798f4f5029589c9b5da73",
"version" : "0.4.2"
"revision" : "25c9b6789b4b7ada649a3808e6d8de1489082a33",
"version" : "0.5.0"
}
},
{
Expand Down
50 changes: 29 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Typically, modeling a CLI tool will begin with a `TopLevelCommandRepresentable`.
```swift
struct MyCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "my-command" }
var flagFormatter: FlagFormatter { .doubleDashPrefix }
var optionFormatter: OptionFormatter { .doubleDashPrefix }
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
let optionFormatter = OptionFormatter(prefix: .doubleDash) }
}
```

Expand All @@ -30,23 +30,39 @@ Within `MyCommand` we need the ability to model a boolean value to enable/disabl
```swift
struct MyCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "my-command" }
var flagFormatter: FlagFormatter { .doubleDashPrefix }
var optionFormatter: OptionFormatter { .doubleDashPrefix }
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
let optionFormatter = OptionFormatter(prefix: .doubleDash) }

@Flag var myFlag: Bool = false
}
```

In addition to modeling the ability to enable/disable a feature, we need to set a value against some variable. For this, we can use `Option`.
In addition to modeling the ability to enable/disable a feature, we need to set a value against some variable. For this, we can use `Option`. For options that can have multiple values, there is `OptionSet`.

```swift
struct MyCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "my-command" }
var flagFormatter: FlagFormatter { .doubleDashPrefix }
var optionFormatter: OptionFormatter { .doubleDashPrefix }
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
let optionFormatter = OptionFormatter(prefix: .doubleDash) }

@Flag var myFlag: Bool = false
@Option var myOption: Int = 0
@OptionSet var myOptions: [String] = ["value1", "value2"]
}
```

Positional arguments that are just a value, with no key are supported through the `Positional` type.

```swift
struct MyCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "my-command" }
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
let optionFormatter = OptionFormatter(prefix: .doubleDash) }

@Flag var myFlag: Bool = false
@Option var myOption: Int = 0
@OptionSet var myOptions: [String] = ["value1", "value2"]
@Positional var myPositional: String = "positional"
}
```

Expand Down Expand Up @@ -114,35 +130,27 @@ import ArgumentEncoding
enum SwiftCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "swift" }

var flagFormatter: FlagFormatter { .doubleDashPrefixKebabCase }
var optionFormatter: OptionFormatter { .doubleDashPrefixKebabCase }
var flagFormatter: FlagFormatter { FlagFormatter(prefix: .doubleDash, body: .kebabCase) }
var optionFormatter: OptionFormatter { OptionFormatter(prefix: .doubleDash, body: .kebabCase) }

case run(RunCommand)
case test(TestCommand)
}

struct RunCommand: CommandRepresentable {
let flagFormatter: FlagFormatter = .doubleDashPrefixKebabCase
let optionFormatter: OptionFormatter = .doubleDashPrefixKebabCase

let executable: Command
@Positional var executable: String
}

extension RunCommand: ExpressibleByStringLiteral {
init(stringLiteral value: StringLiteralType) {
self.init(executable: Command(rawValue: value))
self.init(executable: Positional(wrapped: value))
}
}

struct TestCommand: CommandRepresentable {
let flagFormatter: FlagFormatter = .doubleDashPrefixKebabCase
let optionFormatter: OptionFormatter = .doubleDashPrefixKebabCase

@Flag var parallel: Bool = true
@Option var numWorkers: Int = 1
@Flag var showCodecovPath: Bool = false
var testProducts: [Command]
@OptionSet var testProducts: [String] = []
}

extension [Command]: ArgumentGroup {}
```
```
10 changes: 10 additions & 0 deletions Sources/ArgumentEncoding/ArgumentGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ extension ArgumentGroup {
return container
} else if let option = value as? OptionProtocol {
return .option(option)
} else if let optionSet = value as? OptionSetProtocol {
return .optionSet(optionSet)
} else if let flag = value as? Flag {
return .flag(flag)
} else if let command = value as? Command {
Expand All @@ -110,6 +112,8 @@ extension ArgumentGroup {
return .commandRep(commandRep)
} else if let group = value as? (any ArgumentGroup) {
return .group(group)
} else if let positional = value as? PositionalProtocol {
return .positional(positional)
} else {
return nil
}
Expand All @@ -122,6 +126,8 @@ extension ArgumentGroup {
switch value {
case let .option(option):
return option.arguments(key: label)
case let .optionSet(optionSet):
return optionSet.arguments(key: label)
case let .flag(flag):
return flag.arguments(key: label)
case let .command(command):
Expand All @@ -136,6 +142,8 @@ extension ArgumentGroup {
}
case let .group(group):
return group.arguments()
case let .positional(positional):
return positional.arguments()
}
})
}
Expand Down Expand Up @@ -250,9 +258,11 @@ extension ArgumentGroup {
// Represents the possible underlying argument types
private enum Container {
case option(any OptionProtocol)
case optionSet(any OptionSetProtocol)
case flag(Flag)
case command(Command)
case topLevelCommandRep(any TopLevelCommandRepresentable)
case commandRep(any CommandRepresentable)
case group(any ArgumentGroup)
case positional(any PositionalProtocol)
}
7 changes: 4 additions & 3 deletions Sources/ArgumentEncoding/CaseConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import Foundation

/// Convert from Swift's typical camelCase to kebab-case and snake_case as some argument formats require them.
public enum CaseConverter {
public static let kebabCase: (String) -> String = fromCamelCase(template: "$1-$2")
public static let kebabCase: @Sendable (String) -> String = fromCamelCase(template: "$1-$2")

public static let snakeCase: (String) -> String = fromCamelCase(template: "$1_$2")
public static let snakeCase: @Sendable (String) -> String = fromCamelCase(template: "$1_$2")

private static func fromCamelCase(template: String) -> (String) -> String {
@Sendable
private static func fromCamelCase(template: String) -> @Sendable (String) -> String {
guard let regex = try? NSRegularExpression(pattern: "([a-z0-9])([A-Z])", options: []) else {
return { $0 }
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ArgumentEncoding/CommandRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Dependencies
/// struct ParentGroup: CommandRepresentable {
/// // Formatters to satisfy `FormatterNode` requirements
/// let flagFormatter: FlagFormatter = .doubleDashPrefix
/// let optionFormatter: OptionFormatter = .doubleDashPrefix
/// let optionFormatter: OptionFormatter = OptionFormatter(prefix: .doubleDash)
///
/// // Properties that represent the child arguments
/// @Flag var asyncMain: Bool
Expand Down
2 changes: 1 addition & 1 deletion Sources/ArgumentEncoding/Flag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Dependencies
/// ```swift
/// struct FlagContainer: ArgumentGroup, FormatterNode {
/// let flagFormatter: FlagFormatter = .doubleDashPrefix
/// let optionFormatter: OptionFormatter = .doubleDashPrefix
/// let optionFormatter: OptionFormatter = OptionFormatter(prefix: .doubleDash)
///
/// @Flag var name: Bool = true
/// }
Expand Down
68 changes: 0 additions & 68 deletions Sources/ArgumentEncoding/FlagFormatter.swift

This file was deleted.

Loading

0 comments on commit faa43b0

Please sign in to comment.