Repository: pterm/pterm (go)

Issue:
Providing an odd number of arguments to a pterm Logger's Args method results in a panic
Program:
```golang
package main

import (
	"github.com/pterm/pterm"
)

func main() {
	l := pterm.DefaultLogger
	l.Info("message", l.Args("Hello", ", World!"))
	l.Info("message", l.Args("foo", "bar", "Hello", ", World!"))
	l.Info("message", l.Args(", World!"))
	l.Info("message", l.Args("foo", "bar", ", World!"))
	l.Info("message", l.Args("foo", "bar", "Hello", ", World!", "baz"))
}
```
Output:
```shell
❯ go run ~/Downloads/gotest/main.go
2024-02-01 12:17:38 INFO  message Hello: , World!
2024-02-01 12:17:38 INFO  message foo: bar Hello: , World!
panic: runtime error: index out of range [1] with length 1

goroutine 1 [running]:
github.com/pterm/pterm.Logger.Args({0x0, {0x1010a8608, 0x14000048018}, 0x3, 0x0, 0x0, 0x1, {0x101048ea3, 0x13}, 0x14000072990, ...}, ...)
        /Users/tylergillson/go/pkg/mod/github.com/pterm/pterm@v0.12.76/logger.go:216 +0x1c4
main.main()
        /Users/tylergillson/Downloads/gotest/main.go:11 +0x25c
exit status 2
```

Perhaps this could be handled more gracefully?

Pull request description:
fix: graceful handling of odd args
### Description
Fixes https://github.com/pterm/pterm/issues/628. New output:

![image](https://github.com/pterm/pterm/assets/1795270/73464119-d9a9-499a-95a4-920ec143fd49)

### Scope
pterm logger's `Args` method.

- [X] Bug Fix
- [ ] New Feature
- [ ] Documentation
- [ ] Other

### Related Issue
Fixes #628

### To-Do Checklist
- [X] I tested my changes
- [X] I have commented every method that I created/changed
- [X] I updated the examples to fit with my changes
- [X] I have added tests for my newly created methods


Patch:
diff --git a/errors.go b/errors.go
index a7d6fcc7..9260ab64 100644
--- a/errors.go
+++ b/errors.go
@@ -8,4 +8,7 @@ var (
 
 	// ErrHexCodeIsInvalid - the given HEX code is invalid.
 	ErrHexCodeIsInvalid = errors.New("hex code is not valid")
+
+	// ErrKeyWithoutValue - an odd number of arguments was passed to a pterm Logger's Args method.
+	ErrKeyWithoutValue = "ERROR: key_without_value"
 )
diff --git a/logger.go b/logger.go
index 26eea5b0..e123dda4 100644
--- a/logger.go
+++ b/logger.go
@@ -211,6 +211,8 @@ func (l Logger) Args(args ...any) []LoggerArgument {
 	var loggerArgs []LoggerArgument
 
 	// args are in the format of: key, value, key, value, key, value, ...
+	args = l.sanitizeArgs(args)
+
 	for i := 0; i < len(args); i += 2 {
 		key := Sprint(args[i])
 		value := args[i+1]
@@ -238,6 +240,20 @@ func (l Logger) ArgsFromMap(m map[string]any) []LoggerArgument {
 	return loggerArgs
 }
 
+// sanitizeArgs inserts an error message into an args slice if an odd number of arguments is provided.
+func (l Logger) sanitizeArgs(args []any) []any {
+	numArgs := len(args)
+	if numArgs > 0 && numArgs%2 != 0 {
+		if numArgs > 1 {
+			lastArg := args[numArgs-1]
+			args = append(args[:numArgs-1], []any{ErrKeyWithoutValue, lastArg}...)
+		} else {
+			args = []any{ErrKeyWithoutValue, args[0]}
+		}
+	}
+	return args
+}
+
 func (l Logger) getCallerInfo() (path string, line int) {
 	if !l.ShowCaller {
 		return


Test patch:
diff --git a/logger_test.go b/logger_test.go
new file mode 100644
index 00000000..89376b70
--- /dev/null
+++ b/logger_test.go
@@ -0,0 +1,41 @@
+package pterm
+
+import (
+	"reflect"
+	"testing"
+)
+
+func Test_sanitizeArgs(t *testing.T) {
+	tests := []struct {
+		name     string
+		args     []any
+		expected []any
+	}{
+		{
+			name:     "pass_zero_args",
+			args:     []any{},
+			expected: []any{},
+		},
+		{
+			name:     "pass_one_arg",
+			args:     []any{"foo"},
+			expected: []any{ErrKeyWithoutValue, "foo"},
+		},
+		{
+			name:     "pass_two_args",
+			args:     []any{"foo", "bar"},
+			expected: []any{"foo", "bar"},
+		},
+		{
+			name:     "pass_three_args",
+			args:     []any{"foo", "bar", "baz"},
+			expected: []any{"foo", "bar", ErrKeyWithoutValue, "baz"},
+		},
+	}
+	l := Logger{}
+	for _, tt := range tests {
+		if got := l.sanitizeArgs(tt.args); !reflect.DeepEqual(got, tt.expected) {
+			t.Errorf("sanitizeArgs: got: %v, expected: %v", got, tt.expected)
+		}
+	}
+}