mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
fix: Parse prompt input JSON using object or array chars (#538)
Fixes #492. There is no more single-quote parsing, and instead we use a JSON decoder for multiline values. This is a much better UX!
This commit is contained in:
@ -2,7 +2,9 @@ package cliui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
@ -45,12 +47,22 @@ func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
|
|||||||
} else {
|
} else {
|
||||||
reader := bufio.NewReader(cmd.InOrStdin())
|
reader := bufio.NewReader(cmd.InOrStdin())
|
||||||
line, err = reader.ReadString('\n')
|
line, err = reader.ReadString('\n')
|
||||||
// Multiline with single quotes!
|
|
||||||
if err == nil && strings.HasPrefix(line, "'") {
|
// Check if the first line beings with JSON object or array chars.
|
||||||
rest, err := reader.ReadString('\'')
|
// This enables multiline JSON to be pasted into an input, and have
|
||||||
|
// it parse properly.
|
||||||
|
if err == nil && (strings.HasPrefix(line, "{") || strings.HasPrefix(line, "[")) {
|
||||||
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
|
defer pipeWriter.Close()
|
||||||
|
defer pipeReader.Close()
|
||||||
|
go func() {
|
||||||
|
_, _ = pipeWriter.Write([]byte(line))
|
||||||
|
_, _ = reader.WriteTo(pipeWriter)
|
||||||
|
}()
|
||||||
|
var rawMessage json.RawMessage
|
||||||
|
err := json.NewDecoder(pipeReader).Decode(&rawMessage)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
line += rest
|
line = string(rawMessage)
|
||||||
line = strings.Trim(line, "'")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package cliui_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"runtime"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -47,7 +46,7 @@ func TestPrompt(t *testing.T) {
|
|||||||
require.Equal(t, "yes", <-doneChan)
|
require.Equal(t, "yes", <-doneChan)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Multiline", func(t *testing.T) {
|
t.Run("JSON", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ptty := ptytest.New(t)
|
ptty := ptytest.New(t)
|
||||||
doneChan := make(chan string)
|
doneChan := make(chan string)
|
||||||
@ -59,13 +58,44 @@ func TestPrompt(t *testing.T) {
|
|||||||
doneChan <- resp
|
doneChan <- resp
|
||||||
}()
|
}()
|
||||||
ptty.ExpectMatch("Example")
|
ptty.ExpectMatch("Example")
|
||||||
ptty.WriteLine("'this is a")
|
ptty.WriteLine("{}")
|
||||||
ptty.WriteLine("test'")
|
require.Equal(t, "{}", <-doneChan)
|
||||||
newline := "\n"
|
})
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
newline = "\r\n"
|
t.Run("BadJSON", func(t *testing.T) {
|
||||||
}
|
t.Parallel()
|
||||||
require.Equal(t, "this is a"+newline+"test", <-doneChan)
|
ptty := ptytest.New(t)
|
||||||
|
doneChan := make(chan string)
|
||||||
|
go func() {
|
||||||
|
resp, err := newPrompt(ptty, cliui.PromptOptions{
|
||||||
|
Text: "Example",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
doneChan <- resp
|
||||||
|
}()
|
||||||
|
ptty.ExpectMatch("Example")
|
||||||
|
ptty.WriteLine("{a")
|
||||||
|
require.Equal(t, "{a", <-doneChan)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("MultilineJSON", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ptty := ptytest.New(t)
|
||||||
|
doneChan := make(chan string)
|
||||||
|
go func() {
|
||||||
|
resp, err := newPrompt(ptty, cliui.PromptOptions{
|
||||||
|
Text: "Example",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
doneChan <- resp
|
||||||
|
}()
|
||||||
|
ptty.ExpectMatch("Example")
|
||||||
|
ptty.WriteLine(`{
|
||||||
|
"test": "wow"
|
||||||
|
}`)
|
||||||
|
require.Equal(t, `{
|
||||||
|
"test": "wow"
|
||||||
|
}`, <-doneChan)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,10 +3,11 @@ package cli_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/coder/coder/cli/clitest"
|
"github.com/coder/coder/cli/clitest"
|
||||||
"github.com/coder/coder/coderd/coderdtest"
|
"github.com/coder/coder/coderd/coderdtest"
|
||||||
"github.com/coder/coder/pty/ptytest"
|
"github.com/coder/coder/pty/ptytest"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWorkspaceCreate(t *testing.T) {
|
func TestWorkspaceCreate(t *testing.T) {
|
||||||
|
@ -9,10 +9,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coder/coder/cryptorand"
|
|
||||||
"github.com/ory/dockertest/v3"
|
"github.com/ory/dockertest/v3"
|
||||||
"github.com/ory/dockertest/v3/docker"
|
"github.com/ory/dockertest/v3/docker"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/coder/coder/cryptorand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Required to prevent port collision during container creation.
|
// Required to prevent port collision during container creation.
|
||||||
|
Reference in New Issue
Block a user