Repository: go-go-golems/glazed
Pull request #360 (2023-09-25 03:02:06)

Add flags/arguments parsing from string list
# Add GatherFlagsFromStringList function

## Summary

This PR adds a new `GatherFlagsFromStringList` function to parse command line flags from a string slice without requiring cobra bindings.

## Motivation

Having a utility function to parse flags from strings makes it easy to handle command line arguments programmatically without coupling to a specific command line library like cobra. This function can be useful in testing and also enables parsing flags from arbitrary string slices.

## Changes

- Add `GatherFlagsFromStringList` function
  - Accepts args []string, params []*ParameterDefinition
  - Returns map[string]interface{} of parsed flags
  - Handles boolean, string, int, float, and list variant of each
  - Can ignore required or only include provided flags
  - Gathers remaining args not parsed as flags
- Add tests cases for various argument combinations
- Add new topic to documentation with examples

## Example Usage

```go
params := []*ParameterDefinition{
  {Name: "verbose", ShortFlag: "v", Type: ParameterTypeBool},
  {Name: "output", ShortFlag: "o", Type: ParameterTypeString},
}

args := []string{"--verbose", "-o", "file.txt"} 
result, remainingArgs, err := GatherFlagsFromStringList(args, params, false, false, "")

// result -> map[verbose:true output:file.txt] 
// remainingArgs -> []
```


- :art: Update goreleaser to have new repository: entry instead of tap:
- :sparkles: Add GatherFlagsFromStringList function
- :sparkles: Add onlyProvided/ignoreRequired/prefix to GatherFlagsFromStringList
- :sparkles: :bulb: Add documentation about GatherFlagsFromStringList
- :sparkles: Add pinocchio command to create help pages

Related to #222  but doesn't close it, it's just a helper function on the way.


Patch:
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 45f3658..cfac8f9 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -38,7 +38,7 @@ brews:
 - name: glaze
   description: "Glaze is a tool for converting structured data"
   homepage: "https://github.com/go-go-golems/glazed"
-  tap:
+  repository:
     owner: go-go-golems
     name: homebrew-go-go-go
     token: "{{ .Env.TAP_GITHUB_TOKEN }}"
diff --git a/pinocchio/glazed/create-help.yaml b/pinocchio/glazed/create-help.yaml
new file mode 100644
index 0000000..c9b37eb
--- /dev/null
+++ b/pinocchio/glazed/create-help.yaml
@@ -0,0 +1,129 @@
+name: create-help
+short: Create a markdown file for a help document.
+factories:
+  openai:
+    client:
+      timeout: 120
+    completion:
+      engine: gpt-4
+      temperature: 0.2
+      max_response_tokens: 2048
+      stop: []
+      stream: true
+flags:
+  - name: title
+    type: string
+    help: Title of the document
+  - name: slug
+    type: string
+    help: Slug of the document
+  - name: short
+    type: string
+    help: Short description of the document
+  - name: topics
+    type: stringList
+    help: List of topics related to the document
+  - name: commands
+    type: stringList
+    help: List of command line verbs referred in the document
+  - name: flags
+    type: stringList
+    help: List of command line flags related to the document
+  - name: toplevel
+    type: bool
+    help: If the topic is important enough to be shown at the toplevel
+    default: false
+  - name: per_default
+    type: bool
+    help: If the topic is important enough to be shown as the default help
+    default: false
+  - name: section_type
+    type: choice
+    help: Type of the section
+    choices: [GeneralTopic, Tutorial, Example, Application]
+  - name: example
+    type: string
+    help: Example content for the document
+    default: |
+      ```markdown
+      ---
+      Title: Table Output Format
+      Slug: table-format
+      Short: |
+        Glazed supports a wide variety of table output formats. Besides HTML and Markdown, 
+        it leverages the go-pretty library to provide a rich set of styled terminal output.
+      Topics:
+      - output
+      IsTemplate: false
+      IsTopLevel: false
+      ShowPerDefault: true
+      SectionType: GeneralTopic
+      ---
+
+      Glazed supports a wide variety of table output formats.
+
+      Besides HTML and Markdown, it leverages the go-pretty library to provide a rich set of styled terminal output.
+
+      `--output table` is the default and so wouldn't need to be explicitly specified in most cases.
+
+      ## Markdown output
+
+      Use `--output table --table-format markdown` to output a table in markdown format.
+
+      ``` 
+      ❯ glaze json misc/test-data/[123].json --table-format markdown
+      | b   | c          | a   | d.e | d.f |
+      | --- | ---------- | --- | --- | --- |
+      | 2   | 3, 4, 5    | 1   | 6   | 7   |
+      | 20  | 30, 40, 50 | 10  | 60  | 70  |
+      | 200 | 300        | 100 |     |     |
+      ```
+
+      By piping it into [glow](https://github.com/charmbracelet/glow), you can get a nice looking markdown table:
+
+      ...
+      ```
+  - name: additional_system
+    type: string
+    help: Additional system prompt
+    default: ""
+  - name: additional
+    type: string
+    help: Additional prompt
+    default: ""
+  - name: context
+    type: stringFromFiles
+    help: Additional context from files
+system-prompt: |
+  You are an AI that generates markdown files for help documents. You are knowledgeable about the application and its features. You write clearly and concisely.
+  {{ .additional_system }}
+prompt: |
+  Create a markdown file representing a document in our help system.
+  
+  These documents explain and showcase one specific feature of an application,
+  often with example of how to use it on the command line.
+  
+  The document has a YAML preamble that provides the metadata required by the help system.
+  
+  ---
+  Title: {{ if .title }}{{ .title }}{{ else }}Use the content of the information passed as input to best determine the title.{{ end }}
+  Slug: {{ if .slug }}{{ .slug }}{{ else }}Use the content of the information passed as input to best determine the slug.{{ end }}
+  Short: {{ if .short }}{{ .short }}{{ else }}Use the content of the information passed as input to best determine the short description.{{ end }}
+  Topics: {{ if .topics }}{{ .topics | join ", " }}{{ else }}Use the content of the information passed as input to best determine the topics.{{ end }}
+  Commands: {{ if .commands }}{{ .commands | join ", " }}{{ else }}Use the content of the information passed as input to best determine the command line verbs.{{ end }}
+  Flags: {{ if .flags }}{{ .flags | join ", " }}{{ else }}Use the content of the information passed as input to best determine the command line flags.{{ end }}
+  IsTopLevel: {{ .toplevel }}
+  ShowPerDefault: {{ .per_default }}
+  SectionType: {{ if .section_type }}{{ .section_type }}{{ else }}Use the content of the information passed as input to best determine the type of the section.{{ end }}
+  ---
+  
+  Here is a full example of a help document:
+  {{ .example }}
+  
+  Use the provided information as information about the content of the document.
+  
+  {{ if .context -}}
+  {{ .context }}
+  {{- end }}
+  
+  {{.additional }}
\ No newline at end of file
diff --git a/pkg/cmds/parameters/cobra.go b/pkg/cmds/parameters/cobra.go
index febae83..b57122e 100644
--- a/pkg/cmds/parameters/cobra.go
+++ b/pkg/cmds/parameters/cobra.go
@@ -516,11 +516,24 @@ func GatherFlagsFromViper(
 
 // GatherFlagsFromCobraCommand gathers the flags from the cobra command, and parses them according
 // to the parameter description passed in params. The result is a map of parameter
-// names to parsed values. If onlyProvided is true, only parameters that are provided
+// names to parsed values.
+//
+// If onlyProvided is true, only parameters that are provided
 // by the user are returned (i.e. not the default values).
+//
 // If a parameter cannot be parsed correctly, or is missing even though it is not optional,
 // an error is returned.
-func GatherFlagsFromCobraCommand(cmd *cobra.Command, params []*ParameterDefinition, onlyProvided bool, ignoreRequired bool, prefix string) (map[string]interface{}, error) {
+//
+// The required argument checks that all the required parameter definitions are present.
+// The provided argument only checks that the provided flags are passed.
+// Prefix is prepended to all flag names.
+func GatherFlagsFromCobraCommand(
+	cmd *cobra.Command,
+	params []*ParameterDefinition,
+	onlyProvided bool,
+	ignoreRequired bool,
+	prefix string,
+) (map[string]interface{}, error) {
 	ps := map[string]interface{}{}
 
 	for _, parameter := range params {
diff --git a/pkg/cmds/parameters/strings.go b/pkg/cmds/parameters/strings.go
new file mode 100644
index 0000000..23996db
--- /dev/null
+++ b/pkg/cmds/parameters/strings.go
@@ -0,0 +1,167 @@
+package parameters
+
+import (
+	"fmt"
+	"strings"
+)
+
+// GatherFlagsFromStringList is a function that parses command line flags according to a list of parameter definitions.
+// It returns a map of parameter names to parsed values.
+//
+// ### Usage
+//
+// The function takes two arguments:
+//
+// - `args`: a slice of strings representing the command line arguments.
+// - `params`: a slice of `*ParameterDefinition` representing the parameter definitions.
+//
+// The function returns a map where the keys are the parameter names and the values are the parsed values.
+// If a flag is not recognized or its value cannot be parsed, an error is returned.
+//
+// ### Internals
+//
+// The function first creates a map of possible flag names from the list of parameter definitions.
+// It then iterates over the command line arguments. If an argument starts with `--` or `-`,
+// it is considered a flag. The function then checks if the flag is recognized by looking it up in
+// the map of flag names. If the flag is recognized, its value is collected.
+//
+// The system sets the flag to "true" automatically if it's a boolean flag. If a flag repeats,
+// the system collects all its values in a slice. Once the system has collected all flags and their raw values,
+// it parses the raw values based on the parameter definitions.
+//
+// The `ParseParameter` method of the corresponding `ParameterDefinition` performs the parsing.
+// This method receives a slice of strings and returns the parsed value and an error.
+// The system stores the parsed values in the result map.
+//
+// ### Example
+//
+// Here is an example of how to use the function:
+//
+// ```go
+//
+//	params := []*ParameterDefinition{
+//	   {Name: "verbose", ShortFlag: "v", Type: ParameterTypeBool},
+//	   {Name: "output", ShortFlag: "o", Type: ParameterTypeString},
+//	}
+//
+// args := []string{"--verbose", "-o", "file.txt"}
+// result, args, err := GatherFlagsFromStringList(args, params, false, false, "")
+//
+//	if err != nil {
+//	   log.Fatal(err)
+//	}
+//
+// fmt.Println(result) // prints: map[verbose:true output:file.txt]
+// ```
+//
+// In this example, the function parses the `--verbose` and `-o` flags according to the provided parameter definitions. The `--verbose` flag is a boolean flag and is set to "true". The `-o` flag is a string flag and its value is "file.txt".
+func GatherFlagsFromStringList(
+	args []string,
+	params []*ParameterDefinition,
+	onlyProvided bool,
+	ignoreRequired bool,
+	prefix string,
+) (map[string]interface{}, []string, error) {
+	flagMap := make(map[string]*ParameterDefinition)
+	flagNames := map[string]string{}
+	remainingArgs := []string{}
+
+	for _, param := range params {
+		flagName := prefix + param.Name
+		flagName = strings.ReplaceAll(flagName, "_", "-")
+		if _, ok := flagMap[flagName]; ok {
+			return nil, nil, fmt.Errorf("duplicate flag: --%s", flagName)
+		}
+		flagMap[flagName] = param
+		flagNames[flagName] = param.Name
+
+		if prefix == "" {
+			if param.ShortFlag != "" {
+				if _, ok := flagMap[param.ShortFlag]; ok {
+					return nil, nil, fmt.Errorf("duplicate flag: -%s", param.ShortFlag)
+				}
+				flagMap[param.ShortFlag] = param
+			}
+		}
+	}
+
+	rawValues := make(map[string][]string)
+	for i := 0; i < len(args); i++ {
+		arg := args[i]
+		var flagName string
+		var param *ParameterDefinition
+		var ok bool
+		if strings.HasPrefix(arg, "--") {
+			splitArg := strings.SplitN(arg[2:], "=", 2)
+			flagName = splitArg[0]
+			param, ok = flagMap[flagName]
+			if !ok {
+				return nil, nil, fmt.Errorf("unknown flag: --%s", flagName)
+			}
+			if len(splitArg) == 2 {
+				if IsListParameter(param.Type) {
+					value := strings.Trim(splitArg[1], "[]")
+					values := strings.Split(value, ",")
+					rawValues[flagName] = append(rawValues[flagName], values...)
+				} else {
+					rawValues[flagName] = append(rawValues[flagName], splitArg[1])
+				}
+				continue
+			}
+		} else if strings.HasPrefix(arg, "-") {
+			flagName = arg[1:]
+			param, ok = flagMap[flagName]
+			if !ok {
+				return nil, nil, fmt.Errorf("unknown flag: -%s", flagName)
+			}
+		} else {
+			remainingArgs = append(remainingArgs, arg)
+			continue
+		}
+
+		if param.Type == ParameterTypeBool {
+			rawValues[flagName] = append(rawValues[flagName], "true")
+		} else {
+			if i+1 >= len(args) {
+				return nil, nil, fmt.Errorf("missing value for flag: -%s", flagName)
+			}
+			value := args[i+1]
+			i++
+			if IsListParameter(param.Type) {
+				value = strings.Trim(value, "[]")
+				values := strings.Split(value, ",")
+				rawValues[flagName] = append(rawValues[flagName], values...)
+				continue
+			}
+			rawValues[flagName] = append(rawValues[flagName], value)
+		}
+	}
+
+	result := make(map[string]interface{})
+	for paramName, values := range rawValues {
+		param := flagMap[paramName]
+		if param == nil {
+			return nil, nil, fmt.Errorf("unknown flag: --%s", paramName)
+		}
+		parsedValue, err := param.ParseParameter(values)
+		if err != nil {
+			return nil, nil, fmt.Errorf("invalid value for flag --%s: %v", paramName, err)
+		}
+		result[param.Name] = parsedValue
+	}
+
+	for _, param := range params {
+		if param.Required && !ignoreRequired {
+			if _, ok := rawValues[param.Name]; !ok {
+				return nil, nil, fmt.Errorf("missing required flag: --%s", flagNames[param.Name])
+			}
+		}
+		if !onlyProvided {
+			if _, ok := rawValues[param.Name]; !ok && param.Default != nil {
+				result[param.Name] = param.Default
+			}
+		}
+	}
+
+	return result, remainingArgs, nil
+}
diff --git a/pkg/doc/topics/09-gather-flags-from-string-list.md b/pkg/doc/topics/09-gather-flags-from-string-list.md
new file mode 100644
index 0000000..88c825c
--- /dev/null
+++ b/pkg/doc/topics/09-gather-flags-from-string-list.md
@@ -0,0 +1,112 @@
+---
+Title: Using GatherFlagsFromStringList Function
+Slug: using-gatherflagsfromstringlist
+Short: |
+  Learn how to use the GatherFlagsFromStringList function for parsing command line flags programmatically. This function does not require the use of cobra bindings, but instead just requires a list of strings.
+Topics:
+- Command Line
+- Flags
+- Parsing
+IsTopLevel: false
+ShowPerDefault: false
+SectionType: GeneralTopic
+---
+
+# Using GatherFlagsFromStringList Function
+
+The `GatherFlagsFromStringList` function allows you to parse command line flags programmatically. This function
+does not require the use of cobra bindings, but instead just requires a list of strings.
+
+## Function Signature
+
+```go
+func GatherFlagsFromStringList(
+	args []string,
+	params []*ParameterDefinition,
+	onlyProvided bool,
+	ignoreRequired bool,
+	prefix string,
+) (map[string]interface{}, []string, error)
+```
+
+## Parameters
+
+- `args`: a slice of strings representing the command line arguments.
+- `params`: a slice of `*ParameterDefinition` representing the parameter definitions.
+- `onlyProvided`: a boolean indicating whether to only include flags that were provided in the command line arguments.
+- `ignoreRequired`: a boolean indicating whether to ignore required flags that were not provided in the command line arguments.
+- `prefix`: a string to be added as a prefix to all flag names.
+
+## Return Values
+
+The function returns a map where the keys are the parameter names and the values are the parsed values. If a flag is not recognized or its value cannot be parsed, an error is returned.
+
+## Usage
+
+Here is an example of how to use the function:
+
+```go
+params := []*ParameterDefinition{
+   {Name: "verbose", ShortFlag: "v", Type: ParameterTypeBool},
+   {Name: "output", ShortFlag: "o", Type: ParameterTypeString},
+}
+
+args := []string{"--verbose", "-o", "file.txt"}
+result, args, err := GatherFlagsFromStringList(args, params, false, false, "")
+
+if err != nil {
+   log.Fatal(err)
+}
+
+fmt.Println(result) // prints: map[verbose:true output:file.txt]
+```
+
+In this example, the function parses the `--verbose` and `-o` flags according to the provided parameter definitions. The `--verbose` flag is a boolean flag and is set to "true". The `-o` flag is a string flag and its value is "file.txt".
+
+## Examples
+
+Here are some examples of command line inputs and the corresponding output of the function:
+
+- Input: `--verbose -o file.txt`
+    - Output:
+        - Flags:
+            - verbose: true
+            - output: file.txt
+
+- Input: `--debug --log-level info`
+    - Output:
+        - Flags:
+            - debug: true
+            - log-level: info
+
+- Input: `--output=file.txt --verbose=true`
+    - Output:
+        - Flags:
+            - output: file.txt
+            - verbose: true
+
+- Input: `--size 100 --color red`
+    - Output:
+        - Flags:
+            - size: 100
+            - color: red
+
+- Input: `--int-list=1,2 --int-list 3,4 --int-list=5`
+    - Output:
+        - Flags:
+            - int-list: [1, 2, 3, 4, 5]
+
+- Input: `--string-list item1,item2,item3`
+    - Output:
+        - Flags:
+            - string-list: [item1, item2, item3]
+
+- Input: `--verbose foobar --output file.txt another argument`
+    - Output:
+        - Flags:
+            - verbose: true
+            - output: file.txt
+        - Args:
+            - foobar
+            - another
+            - argument


Test patch:
diff --git a/pkg/cmds/parameters/strings_test.go b/pkg/cmds/parameters/strings_test.go
new file mode 100644
index 0000000..48a40a3
--- /dev/null
+++ b/pkg/cmds/parameters/strings_test.go
@@ -0,0 +1,518 @@
+package parameters
+
+import (
+	"reflect"
+	"testing"
+)
+
+// TestGatherFlagsFromStringList_ValidArgumentsAndParameters tests the function with valid arguments and parameters.
+func TestGatherFlagsFromStringList_ValidArgumentsAndParameters(t *testing.T) {
+	tests := []struct {
+		name           string
+		args           []string
+		params         []*ParameterDefinition
+		want           map[string]interface{}
+		wantArgs       []string
+		wantErr        bool
+		ignoreRequired bool
+		onlyProvided   bool
+		prefix         string
+	}{
+		// "--verbose -o file.txt" (bool, string)
+		{
+			name: "bool, string",
+			args: []string{"--verbose", "-o", "file.txt"},
+			params: []*ParameterDefinition{
+				{Name: "verbose", ShortFlag: "v", Type: ParameterTypeBool},
+				{Name: "output", ShortFlag: "o", Type: ParameterTypeString},
+			},
+			want: map[string]interface{}{
+				"verbose": true,
+				"output":  "file.txt",
+			},
+			wantErr: false,
+		},
+		// "--debug --log-level info" (bool, string)
+		{
+			name: "bool, string",
+			args: []string{"--debug", "--log-level", "info"},
+			params: []*ParameterDefinition{
+				{Name: "debug", ShortFlag: "", Type: ParameterTypeBool},
+				{Name: "log-level", ShortFlag: "l", Type: ParameterTypeString},
+			},
+			want: map[string]interface{}{
+				"debug":     true,
+				"log-level": "info",
+			},
+			wantErr: false,
+		},
+		// "-d -l info" (bool, string, using short flags)
+		{
+			name: "bool, string short flags",
+			args: []string{"-d", "-l", "info"},
+			params: []*ParameterDefinition{
+				{Name: "debug", ShortFlag: "d", Type: ParameterTypeBool},
+				{Name: "log-level", ShortFlag: "l", Type: ParameterTypeString},
+			},
+			want: map[string]interface{}{
+				"debug":     true,
+				"log-level": "info",
+			},
+			wantErr: false,
+		},
+		// "--output=file.txt --verbose=true" (string, bool, using =)
+		{
+			name: "string, bool using =",
+			args: []string{"--output=file.txt", "--verbose=true"},
+			params: []*ParameterDefinition{
+				{Name: "output", ShortFlag: "o", Type: ParameterTypeString},
+				{Name: "verbose", ShortFlag: "v", Type: ParameterTypeBool},
+			},
+			want: map[string]interface{}{
+				"output":  "file.txt",
+				"verbose": true,
+			},
+			wantErr: false,
+		},
+		// "--verbose=false" (bool false)
+		{
+			name: "bool false",
+			args: []string{"--verbose=false"},
+			params: []*ParameterDefinition{
+				{Name: "verbose", ShortFlag: "v", Type: ParameterTypeBool},
+			},
+			want: map[string]interface{}{
+				"verbose": false,
+			},
+		},
+		// "--output file.txt -v" (string, bool, different order)
+		{
+			name: "string, bool",
+			args: []string{"--output", "file.txt", "-v"},
+			params: []*ParameterDefinition{
+				{Name: "output", ShortFlag: "o", Type: ParameterTypeString},
+				{Name: "verbose", ShortFlag: "v", Type: ParameterTypeBool},
+			},
+			want: map[string]interface{}{
+				"output":  "file.txt",
+				"verbose": true,
+			},
+			wantErr: false,
+		},
+		// "--output file.txt --verbose" (string, bool, different order)
+		{
+			name: "string, bool another order",
+			args: []string{"--output", "file.txt", "--verbose"},
+			params: []*ParameterDefinition{
+				{Name: "output", ShortFlag: "o", Type: ParameterTypeString},
+				{Name: "verbose", ShortFlag: "v", Type: ParameterTypeBool},
+			},
+			want: map[string]interface{}{
+				"output":  "file.txt",
+				"verbose": true,
+			},
+			wantErr: false,
+		},
+		// "--size 100 --color red" (integer, string)
+		{
+			name: "integer, string",
+			args: []string{"--size", "100", "--color", "red"},
+			params: []*ParameterDefinition{
+				{Name: "size", ShortFlag: "s", Type: ParameterTypeInteger},
+				{Name: "color", ShortFlag: "c", Type: ParameterTypeString},
+			},
+			want: map[string]interface{}{
+				"size":  100,
+				"color": "red",
+			},
+			wantErr: false,
+		},
+		// "--size 100 -c red" (integer, string, using short flag)
+		{
+			name: "integer, string short flag",
+			args: []string{"--size", "100", "-c", "red"},
+			params: []*ParameterDefinition{
+				{Name: "size", ShortFlag: "s", Type: ParameterTypeInteger},
+				{Name: "color", ShortFlag: "c", Type: ParameterTypeString},
+			},
+			want: map[string]interface{}{
+				"size":  100,
+				"color": "red",
+			},
+			wantErr: false,
+		},
+		// "--enable-feature" (bool, no value)
+		{
+			name: "bool no value",
+			args: []string{"--enable-feature"},
+			params: []*ParameterDefinition{
+				{Name: "enable-feature", ShortFlag: "e", Type: ParameterTypeBool},
+			},
+			want: map[string]interface{}{
+				"enable-feature": true,
+			},
+			wantErr: false,
+		},
+		// "-e" (bool, no value, using short flag)
+		{
+			name: "bool short flag no value",
+			args: []string{"-e"},
+			params: []*ParameterDefinition{
+				{Name: "enable-feature", ShortFlag: "e", Type: ParameterTypeBool},
+			},
+			want: map[string]interface{}{
+				"enable-feature": true,
+			},
+			wantErr: false,
+		},
+		// "--float 3.14" (float)
+		{
+			name: "float",
+			args: []string{"--float", "3.14"},
+			params: []*ParameterDefinition{
+				{Name: "float", ShortFlag: "f", Type: ParameterTypeFloat},
+			},
+			want: map[string]interface{}{
+				"float": 3.14,
+			},
+			wantErr: false,
+		},
+		// "--int-list=1,2 --int-list 3,4 --int-list=5
+		{
+			name: "int list",
+			args: []string{"--int-list=1,2", "--int-list", "3,4", "--int-list=5"},
+			params: []*ParameterDefinition{
+				{Name: "int-list", ShortFlag: "i", Type: ParameterTypeIntegerList},
+			},
+			want: map[string]interface{}{
+				"int-list": []int{1, 2, 3, 4, 5},
+			},
+		},
+		// "--choice A" (choice)
+		{
+			name: "choice",
+			args: []string{"--choice", "A"},
+			params: []*ParameterDefinition{
+				{Name: "choice", ShortFlag: "c", Type: ParameterTypeChoice, Choices: []string{"A", "B", "C"}},
+			},
+			want: map[string]interface{}{
+				"choice": "A",
+			},
+			wantErr: false,
+		},
+		// "--string-list item1,item2,item3" (string list)
+		{
+			name: "string list",
+			args: []string{"--string-list", "item1,item2,item3"},
+			params: []*ParameterDefinition{
+				{Name: "string-list", ShortFlag: "s", Type: ParameterTypeStringList},
+			},
+			want: map[string]interface{}{
+				"string-list": []string{"item1", "item2", "item3"},
+			},
+			wantErr: false,
+		},
+		// "--string-list item1 --string-list item2 --string-list item3" (string list)
+		{
+			name: "multiple string list",
+			args: []string{"--string-list", "item1", "--string-list", "item2", "--string-list", "item3"},
+			params: []*ParameterDefinition{
+				{Name: "string-list", ShortFlag: "s", Type: ParameterTypeStringList},
+			},
+			want: map[string]interface{}{
+				"string-list": []string{"item1", "item2", "item3"},
+			},
+			wantErr: false,
+		},
+		// "--string-list item1 --integer 1 --string-list item2 --string-list item3" (string list, integer)
+		{
+			name: "string list with integer",
+			args: []string{"--string-list", "item1", "--integer", "1", "--string-list", "item2", "--string-list", "item3"},
+			params: []*ParameterDefinition{
+				{Name: "string-list", ShortFlag: "s", Type: ParameterTypeStringList},
+				{Name: "integer", ShortFlag: "i", Type: ParameterTypeInteger},
+			},
+			want: map[string]interface{}{
+				"string-list": []string{"item1", "item2", "item3"},
+				"integer":     1,
+			},
+			wantErr: false,
+		},
+		// "--integer-list 1,2,3" (integer list)
+		{
+			name: "integer list",
+			args: []string{"--integer-list", "1,2,3"},
+			params: []*ParameterDefinition{
+				{Name: "integer-list", ShortFlag: "il", Type: ParameterTypeIntegerList},
+			},
+			want: map[string]interface{}{
+				"integer-list": []int{1, 2, 3},
+			},
+			wantErr: false,
+		},
+		// "--float-list 1.1,2.2,3.3" (float list)
+		{
+			name: "float list",
+			args: []string{"--float-list", "1.1,2.2,3.3"},
+			params: []*ParameterDefinition{
+				{Name: "float-list", ShortFlag: "fl", Type: ParameterTypeFloatList},
+			},
+			want: map[string]interface{}{
+				"float-list": []float64{1.1, 2.2, 3.3},
+			},
+			wantErr: false,
+		},
+		// "--float-list 1.1 --float-list 2 --float-list 3.3"
+		{
+			name: "multiple float list",
+			args: []string{"--float-list", "1.1", "--float-list", "2", "--float-list", "3.3"},
+			params: []*ParameterDefinition{
+				{Name: "float-list", ShortFlag: "fl", Type: ParameterTypeFloatList},
+			},
+			want: map[string]interface{}{
+				"float-list": []float64{1.1, 2, 3.3},
+			},
+			wantErr: false,
+		},
+		// "--choice-list A,B,C" (choice list)
+		{
+			name: "choice list",
+			args: []string{"--choice-list", "A,B,C"},
+			params: []*ParameterDefinition{
+				{Name: "choice-list", ShortFlag: "cl", Type: ParameterTypeChoiceList, Choices: []string{"A", "B", "C"}},
+			},
+			want: map[string]interface{}{
+				"choice-list": []string{"A", "B", "C"},
+			},
+			wantErr: false,
+		},
+		// "--key-value key1=value1;key2=value2" (key-value)
+		{
+			name: "key-value",
+			args: []string{"--key-value", "key1:value1,key2:value2"},
+			params: []*ParameterDefinition{
+				{Name: "key-value", ShortFlag: "kv", Type: ParameterTypeKeyValue},
+			},
+			want: map[string]interface{}{
+				"key-value": map[string]interface{}{
+					"key1": "value1",
+					"key2": "value2",
+				},
+			},
+			wantErr: false,
+		},
+		// "--unknownFlag value" (unknown flag)
+		{
+			name: "UnknownFlag",
+			args: []string{"--unknownFlag", "value"},
+			params: []*ParameterDefinition{
+				{Name: "knownFlag", ShortFlag: "k", Type: ParameterTypeString},
+			},
+			wantErr: true,
+		},
+		// "--flag" (missing value for non-boolean flag)
+		{
+			name: "MissingValueForNonBooleanFlag",
+			args: []string{"--flag"},
+			params: []*ParameterDefinition{
+				{Name: "flag", ShortFlag: "f", Type: ParameterTypeString},
+			},
+			wantErr: true,
+		},
+		// "--size invalidValue" (invalid value for flag)
+		{
+			name: "InvalidValueForFlag",
+			args: []string{"--size", "invalidValue"},
+			params: []*ParameterDefinition{
+				{Name: "size", ShortFlag: "s", Type: ParameterTypeInteger},
+			},
+			wantErr: true,
+		},
+		// "--flag value1 --flag value2" (repeated flags)
+		{
+			name: "RepeatedFlags",
+			args: []string{"--flag", "value1", "--flag", "value2"},
+			params: []*ParameterDefinition{
+				{Name: "flag", ShortFlag: "f", Type: ParameterTypeString},
+			},
+			wantErr: true,
+		},
+		// "--flag value1 -f value2" (repeated flags)
+		{
+			name: "BooleanFlags",
+			args: []string{"--flag", "--anotherFlag"},
+			params: []*ParameterDefinition{
+				{Name: "flag", ShortFlag: "f", Type: ParameterTypeBool},
+				{Name: "anotherFlag", ShortFlag: "a", Type: ParameterTypeBool},
+			},
+			want: map[string]interface{}{
+				"flag":        true,
+				"anotherFlag": true,
+			},
+			wantErr: false,
+		},
+		// "" (empty arguments)
+		{
+			name: "EmptyArguments",
+			args: []string{},
+			params: []*ParameterDefinition{
+				{Name: "flag", ShortFlag: "f", Type: ParameterTypeString},
+			},
+			want:    map[string]interface{}{},
+			wantErr: false,
+		},
+		// "--flag value" (empty parameters)
+		{
+			name:    "EmptyParameters",
+			args:    []string{"--flag", "value"},
+			params:  []*ParameterDefinition{},
+			wantErr: true,
+		},
+		// "--flag value" (parameters with empty ShortFlag)
+		{
+			name: "ParametersWithEmptyShortFlag",
+			args: []string{"--flag", "value"},
+			params: []*ParameterDefinition{
+				{Name: "flag", ShortFlag: "", Type: ParameterTypeString},
+			},
+			want: map[string]interface{}{
+				"flag": "value",
+			},
+			wantErr: false,
+		},
+		// "--flag value -f value" (parameters with the same Name and ShortFlag)
+		{
+			name: "ParametersWithSameNameAndShortFlag",
+			args: []string{"--flag", "value", "-f", "value"},
+			params: []*ParameterDefinition{
+				{Name: "flag", ShortFlag: "f", Type: ParameterTypeString},
+				{Name: "flag", ShortFlag: "f", Type: ParameterTypeString},
+			},
+			wantErr: true,
+		},
+		// "--flag value -a value" (parameters with the same Name but different ShortFlag)
+		{
+			name: "ParametersWithSameNameDifferentShortFlag",
+			args: []string{"--flag", "value", "-a", "value"},
+			params: []*ParameterDefinition{
+				{Name: "flag", ShortFlag: "f", Type: ParameterTypeString},
+				{Name: "flag", ShortFlag: "a", Type: ParameterTypeString},
+			},
+			wantErr: true,
+		},
+		// "--flag1 value1 -f value2" (parameters with different Name but the same ShortFlag)
+		{
+			name: "ParametersWithDifferentNameSameShortFlag",
+			args: []string{"-f", "value1", "-f", "value2"},
+			params: []*ParameterDefinition{
+				{Name: "flag1", ShortFlag: "f", Type: ParameterTypeString},
+				{Name: "flag2", ShortFlag: "f", Type: ParameterTypeString},
+			},
+			wantErr: true,
+		},
+		// "--flag1 value1 --flag2 value2" (mix of valid and invalid parameters)
+		{
+			name: "MixOfValidAndInvalidParameters",
+			args: []string{"--flag1", "value1", "--flag2", "value2"},
+			params: []*ParameterDefinition{
+				{Name: "flag1", ShortFlag: "f", Type: ParameterTypeString},
+				// Assuming invalid parameter doesn't have a type
+				{Name: "flag2", ShortFlag: "g"},
+			},
+			wantErr: true,
+		},
+		// "--prefix-integer 1 --prefix-foobar 2" (prefix: integer, integer)
+		{
+			name: "Prefix: integer, integer",
+			args: []string{"--prefix-integer", "1", "--prefix-foobar", "2"},
+			params: []*ParameterDefinition{
+				{Name: "integer", ShortFlag: "i", Type: ParameterTypeInteger},
+				{Name: "foobar", ShortFlag: "f", Type: ParameterTypeInteger},
+			},
+			want: map[string]interface{}{
+				"integer": 1,
+				"foobar":  2,
+			},
+			wantErr: false,
+			prefix:  "prefix-",
+		},
+		// "--foobar-bla foo" (param name with _: foobar_bla)
+		{
+			name: "Param name with _: foobar_bla",
+			args: []string{"--foobar-bla", "foo"},
+			params: []*ParameterDefinition{
+				{Name: "foobar_bla", ShortFlag: "f", Type: ParameterTypeString},
+			},
+			want: map[string]interface{}{
+				"foobar_bla": "foo",
+			},
+			wantErr: false,
+		},
+		// "required argument: --required foo" (required argument)
+		{
+			name: "Required argument",
+			args: []string{"--required", "foo"},
+			params: []*ParameterDefinition{
+				{Name: "required", ShortFlag: "r", Type: ParameterTypeString, Required: true},
+			},
+			want: map[string]interface{}{
+				"required": "foo",
+			},
+			wantErr: false,
+		},
+		// error when required argument is missing
+		{
+			name: "Required argument missing",
+			args: []string{},
+			params: []*ParameterDefinition{
+				{Name: "required", ShortFlag: "r", Type: ParameterTypeString, Required: true},
+			},
+			wantErr: true,
+		},
+		// "foobar: default 2, but missing on command line" (default value)
+		{
+			name: "Default value",
+			args: []string{},
+			params: []*ParameterDefinition{
+				{Name: "foobar", ShortFlag: "f", Type: ParameterTypeInteger, Default: 2},
+			},
+			want: map[string]interface{}{
+				"foobar": 2,
+			},
+			wantErr: false,
+		},
+		// gather arguments: --verbose foobar --output file.txt another argument
+		{
+			name: "Gather arguments",
+			args: []string{"--verbose", "foobar", "--output", "file.txt", "another", "argument"},
+			params: []*ParameterDefinition{
+				{Name: "verbose", ShortFlag: "v", Type: ParameterTypeBool},
+				{Name: "output", ShortFlag: "o", Type: ParameterTypeString},
+			},
+			want: map[string]interface{}{
+				"verbose": true,
+				"output":  "file.txt",
+			},
+			wantArgs: []string{"foobar", "another", "argument"},
+			wantErr:  false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, args, err := GatherFlagsFromStringList(tt.args, tt.params, tt.onlyProvided, tt.ignoreRequired, tt.prefix)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GatherFlagsFromStringList() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GatherFlagsFromStringList() = %v, want %v", got, tt.want)
+			}
+			if tt.wantArgs != nil {
+				if !reflect.DeepEqual(args, tt.wantArgs) {
+					t.Errorf("GatherFlagsFromStringList() = %v, want %v", args, tt.wantArgs)
+				}
+			}
+		})
+	}
+}