Repository: apple/swift-argument-parser
Pull request #486 (2022-09-08 14:18:04)

Fix default display in help for `EnumerableFlag` and other types
When an `EnumerableFlag` flag has a default value, the default is printed incorrectly as the instance value in the help, instead of as the flag that a user would need to type. The same issue is present for Boolean flags with both an inversion and a default.

```swift
struct Example: ParsableCommand {
   @Flag(inversion: .prefixedNo)
   var frobulate: Bool = true
}
```

Old help output:

```
USAGE: example [--frobulate] [--no-frobulate]

OPTIONS:
  --frobulate/--no-frobulate
                          Whether to frobulate. (default: true)
  -h, --help              Show help information.
```

Corrected help output:

```
USAGE: example [--frobulate] [--no-frobulate]

OPTIONS:
  --frobulate/--no-frobulate
                          Whether to frobulate. (default: --frobulate)
  -h, --help              Show help information.
```

### Checklist
- [x] I've added at least one test that validates that my change is working, if appropriate
- [x] I've followed the code style of the rest of the project
- [x] I've read the [Contribution Guidelines](https://github.com/apple/swift-argument-parser/blob/main/CONTRIBUTING.md)
- [x] I've updated the documentation if necessary


rauhul: Ooh this is a nice fix!
natecook1000: Will almost certainly conflict with #477, let's land that first!
natecook1000: @swift-ci Please test

Patch:
diff --git a/Sources/ArgumentParser/Parsable Properties/Flag.swift b/Sources/ArgumentParser/Parsable Properties/Flag.swift
index 249ac40..4d31c05 100644
--- a/Sources/ArgumentParser/Parsable Properties/Flag.swift
+++ b/Sources/ArgumentParser/Parsable Properties/Flag.swift
@@ -391,7 +391,15 @@ extension Flag where Value: EnumerableFlag {
       // This gets flipped to `true` the first time one of these flags is
       // encountered.
       var hasUpdated = false
-      let defaultValue = initial.map(String.init(describing:))
+      
+      // Create a string representation of the default value. Since this is a
+      // flag, the default value to show to the user is the `--value-name`
+      // flag that a user would provide on the command line, not a Swift value.
+      let defaultValueFlag = initial.flatMap { value in
+        let defaultKey = InputKey(rawValue: String(describing: value))
+        let defaultNames = Value.name(for: value).makeNames(defaultKey)
+        return defaultNames.first?.synopsisString
+      }
 
       let caseHelps = Value.allCases.map { Value.help(for: $0) }
       let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil })
@@ -399,17 +407,36 @@ extension Flag where Value: EnumerableFlag {
       let args = Value.allCases.enumerated().map { (i, value) -> ArgumentDefinition in
         let caseKey = InputKey(rawValue: String(describing: value))
         let name = Value.name(for: value)
-        let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help
+        
+        let helpForCase = caseHelps[i] ?? help
+        var defaultValueString: String? = nil
+        if hasCustomCaseHelp {
+          if value == initial {
+            defaultValueString = defaultValueFlag
+          }
+        } else {
+          defaultValueString = defaultValueFlag
+        }
+        
         let help = ArgumentDefinition.Help(
           allValues: [],
-          options: initial != nil ? [.isOptional] : [],
+          options: initial != nil ? .isOptional : [],
           help: helpForCase,
-          defaultValue: defaultValue,
+          defaultValue: defaultValueString,
           key: key,
           isComposite: !hasCustomCaseHelp)
-        return ArgumentDefinition.flag(name: name, key: key, caseKey: caseKey, help: help, parsingStrategy: .default, initialValue: initial, update: .nullary({ (origin, name, values) in
-          hasUpdated = try ArgumentSet.updateFlag(key: key, value: value, origin: origin, values: &values, hasUpdated: hasUpdated, exclusivity: exclusivity)
-        }))
+        
+        return ArgumentDefinition.flag(
+          name: name,
+          key: key,
+          caseKey: caseKey,
+          help: help,
+          parsingStrategy: .default,
+          initialValue: initial,
+          update: .nullary({ (origin, name, values) in
+            hasUpdated = try ArgumentSet.updateFlag(key: key, value: value, origin: origin, values: &values, hasUpdated: hasUpdated, exclusivity: exclusivity)
+          })
+        )
       }
       return ArgumentSet(args)
       })
diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift
index 4c7c88e..3a3256a 100644
--- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift
+++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift
@@ -122,12 +122,15 @@ extension ArgumentSet {
     help: ArgumentHelp?) -> ArgumentSet
   {
     let helpOptions: ArgumentDefinition.Help.Options = required ? [] : .isOptional
+        
+    let (enableNames, disableNames) = inversion.enableDisableNamePair(for: key, name: name)
+    let initialValueNames = initialValue.map {
+      $0 ? enableNames : disableNames
+    }
     
-    let enableHelp = ArgumentDefinition.Help(allValues: [], options: helpOptions, help: help, defaultValue: initialValue.map(String.init), key: key, isComposite: true)
+    let enableHelp = ArgumentDefinition.Help(allValues: [], options: helpOptions, help: help, defaultValue: initialValueNames?.first?.synopsisString, key: key, isComposite: true)
     let disableHelp = ArgumentDefinition.Help(allValues: [], options: [.isOptional], help: help, defaultValue: nil, key: key, isComposite: false)
 
-    let (enableNames, disableNames) = inversion.enableDisableNamePair(for: key, name: name)
-
     var hasUpdated = false
     let enableArg = ArgumentDefinition(kind: .named(enableNames), help: enableHelp, completion: .default, update: .nullary({ (origin, name, values) in
         hasUpdated = try ArgumentSet.updateFlag(key: key, value: true, origin: origin, values: &values, hasUpdated: hasUpdated, exclusivity: exclusivity)


Test patch:
diff --git a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift
index dd4403d..89c16e3 100644
--- a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift
+++ b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift
@@ -162,11 +162,11 @@ enum Size: String, EnumerableFlag {
   static func help(for value: Size) -> ArgumentHelp? {
     switch value {
     case .small:
-      return "A smallish size"
+      return "A smallish size."
     case .medium:
-      return "Not too big, not too small"
+      return "Not too big, not too small."
     case .humongous:
-      return "Roughly the size of a barge"
+      return "Roughly the size of a barge."
     case .large, .extraLarge:
       return nil
     }
@@ -183,7 +183,7 @@ fileprivate struct Baz: ParsableArguments {
   @Flag()
   var color: Color
 
-  @Flag
+  @Flag(help: "The size to use.")
   var size: Size = .small
 
   @Flag()
@@ -257,6 +257,23 @@ extension FlagsEndToEndTests {
     }
   }
 
+  func testParsingCaseIterable_Help() throws {
+    AssertHelp(.default, for: Baz.self, equals: """
+            USAGE: baz --pink --purple --silver [--small] [--medium] [--large] [--extra-large] [--humongous] [--round] [--square] [--oblong]
+
+            OPTIONS:
+              --pink/--purple/--silver
+              -s, --small             A smallish size. (default: --small)
+              -m, --medium            Not too big, not too small.
+              -l, --large             The size to use.
+              --extra-large           The size to use.
+              --humongous, --huge     Roughly the size of a barge.
+              --round/--square/--oblong
+              -h, --help              Show help information.
+            
+            """)
+  }
+  
   func testParsingCaseIterable_Fails() throws {
     // Missing color
     XCTAssertThrowsError(try Baz.parse([]))
diff --git a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift
index aa52bf8..dd6ad2d 100644
--- a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift
+++ b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift
@@ -119,21 +119,24 @@ extension HelpTests {
                                           Build with configuration (default: debug)
                   --enable-automatic-resolution/--disable-automatic-resolution
                                           Use automatic resolution if Package.resolved file is
-                                          out-of-date (default: true)
+                                          out-of-date (default: --enable-automatic-resolution)
                   --enable-index-store/--disable-index-store
-                                          Use indexing-while-building feature (default: true)
+                                          Use indexing-while-building feature (default:
+                                          --enable-index-store)
                   --enable-package-manifest-caching/--disable-package-manifest-caching
-                                          Cache Package.swift manifests (default: true)
+                                          Cache Package.swift manifests (default:
+                                          --enable-package-manifest-caching)
                   --enable-prefetching/--disable-prefetching
-                                          (default: true)
+                                          (default: --enable-prefetching)
                   --enable-sandbox/--disable-sandbox
                                           Use sandbox when executing subprocesses (default:
-                                          true)
+                                          --enable-sandbox)
                   --enable-pubgrub-resolver/--disable-pubgrub-resolver
                                           [Experimental] Enable the new Pubgrub dependency
-                                          resolver (default: false)
+                                          resolver (default: --disable-pubgrub-resolver)
                   --static-swift-stdlib/--no-static-swift-stdlib
-                                          Link Swift stdlib statically (default: false)
+                                          Link Swift stdlib statically (default:
+                                          --no-static-swift-stdlib)
                   --package-path <package-path>
                                           Change working directory before any other operation
                                           (default: .)
diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift
index aca783e..946d178 100644
--- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift
+++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift
@@ -84,7 +84,7 @@ extension HelpGenerationTests {
               --hidden-title <hidden-title>
               --hidden-flag
               --hidden-inverted-flag/--no-hidden-inverted-flag
-                                      (default: true)
+                                      (default: --hidden-inverted-flag)
               -h, --help              Show help information.
 
             """)
@@ -207,7 +207,7 @@ extension HelpGenerationTests {
         --age <age>             Your age. (default: 20)
         --logging <logging>     Whether logging is enabled. (default: false)
         --lucky <numbers>       Your lucky numbers. (default: 7, 14)
-        --optional/--required   Vegan diet. (default: optional)
+        --optional/--required   Vegan diet. (default: --optional)
         --degree <degree>       Your degree.
         --directory <directory> Directory. (default: current directory)
         --manual <manual>       Manual Option. (default: default-value)
@@ -264,7 +264,7 @@ extension HelpGenerationTests {
                USAGE: f [-s] [-c] [-l]
 
                OPTIONS:
-                 -s/-c/-l                Change the program output (default: list)
+                 -s/-c/-l                Change the program output (default: -l)
                  -h, --help              Show help information.
 
                """)
@@ -273,7 +273,7 @@ extension HelpGenerationTests {
                USAGE: g [--flag] [--no-flag]
 
                OPTIONS:
-                 --flag/--no-flag        Whether to flag (default: false)
+                 --flag/--no-flag        Whether to flag (default: --no-flag)
                  -h, --help              Show help information.
 
                """)