Skip to content

Commit

Permalink
Compact json in examples
Browse files Browse the repository at this point in the history
  • Loading branch information
judimator committed May 8, 2024
1 parent b055024 commit d0e4dee
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 131 deletions.
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Available Commands:
Flags:
-h, --help Help
-i, --indent Indent
--version Current Augurken version
Use "augurken [command] --help" for more information about a command.
Expand Down Expand Up @@ -43,8 +44,9 @@ $ augurken format -i 2 /path/to/filename.feature
# Features
- Format Gherkin features
- Format JSON in step doc string
- Scenario Outline. Recognize and compact JSON inside table

## Supported JSON format
## Supported JSON format in step doc string

```json
{
Expand Down Expand Up @@ -74,6 +76,40 @@ $ augurken format -i 2 /path/to/filename.feature
]
```

## Compact JSON inside table

Examples like

```gherkin
Feature: The feature
Scenario Outline: Compact json
Given I load data:
"""
<data>
"""
Examples:
|data |
| {"key1": "value2", "key2": "value2"} |
| [1, 2, 3] |
```

become

```gherkin
Feature: The feature
Scenario Outline: Compact json
Given I load data:
"""
<data>
"""
Examples:
|data |
| {"key1":"value2","key2":"value2"} |
| [1,2,3] |
```

# Contribute<a id="contribute"></a>

If you want to add a new feature, open an issue with proposal
42 changes: 42 additions & 0 deletions formatter/file_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,48 @@ hello world
assert.EqualValues(t, content, string(b))
},
},
{
"compact json in example",
"./tmp/file1.feature",
func() {
content := []byte(`Feature: test feature
Scenario Outline: Compact json
Given I load data:
"""
<data>
"""
Examples:
| data |
|{"key1": "value2", "key2": "value2"}|
|[1, 2, 3] |
`)

assert.NoError(t, os.RemoveAll("./tmp/"))
assert.NoError(t, os.MkdirAll("./tmp/", 0o777))
assert.NoError(t, os.WriteFile("./tmp/file1.feature", content, 0o777))
},
func(err error) {
assert.NoError(t, err)

content := `Feature: test feature
Scenario Outline: Compact json
Given I load data:
"""
<data>
"""
Examples:
| data |
| {"key1":"value2","key2":"value2"} |
| [1,2,3] |
`

b, e := os.ReadFile("./tmp/file1.feature")
assert.NoError(t, e)
assert.EqualValues(t, content, string(b))
},
},
{
"format a folder",
"./tmp/",
Expand Down
28 changes: 18 additions & 10 deletions formatter/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package formatter

import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
"unicode/utf8"

gherkin "github.com/cucumber/gherkin/go/v28"
"github.com/judimator/augurken/json"
augurkenjson "github.com/judimator/augurken/json"
)

func format(token *token, indent int) ([]byte, error) {
Expand Down Expand Up @@ -87,17 +88,14 @@ func format(token *token, indent int) ([]byte, error) {

// Transform into string and get bytes
source := []byte(strings.Join(lines, " "))
prefixSpace := strings.Repeat(" ", padding)
indentSpace := strings.Repeat(" ", indent)

if ok := json.Valid(source); ok == true {
_ = json.Indent(
&buffer,
source,
strings.Repeat(" ", padding),
strings.Repeat(" ", indent),
)
if ok := augurkenjson.Valid(source); ok == true {
_ = augurkenjson.Indent(&buffer, source, prefixSpace, indentSpace)
lines = []string{string(buffer.Bytes())}
}
// TODO: Handle json error and print col and line with it
// TODO: Handle json error and print col and line
}

lines = trimLinesSpace(lines)
Expand Down Expand Up @@ -223,9 +221,19 @@ func extractTableRowsAndComments(tokens []*gherkin.Token) []string {
} else {
var row []string
for _, data := range token.Items {
var text string

source := []byte(data.Text)
if ok := json.Valid(source); ok == true {
var buffer bytes.Buffer
_ = json.Compact(&buffer, source)
text = buffer.String()
} else {
text = data.Text
}

// A remaining pipe means it was escaped before to not be messed with pipe column delimiter
// so here we introduce the escaping sequence back
text := data.Text
text = strings.ReplaceAll(text, "\\", "\\\\")
text = strings.ReplaceAll(text, "\n", "\\n")
text = strings.ReplaceAll(text, "|", "\\|")
Expand Down
53 changes: 0 additions & 53 deletions json/indent.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,59 +8,6 @@ import (
"bytes"
)

const hex = "0123456789abcdef"

// Compact appends to dst the JSON-encoded src with
// insignificant space characters elided.
func Compact(dst *bytes.Buffer, src []byte) error {
dst.Grow(len(src))
b := dst.AvailableBuffer()
b, err := appendCompact(b, src, false)
dst.Write(b)
return err
}

func appendCompact(dst, src []byte, escape bool) ([]byte, error) {
origLen := len(dst)
scan := newScanner()
defer freeScanner(scan)
start := 0
for i, c := range src {
if escape && (c == '<' || c == '>' || c == '&') {
if start < i {
dst = append(dst, src[start:i]...)
}
dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
start = i + 1
}
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
if escape && c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
if start < i {
dst = append(dst, src[start:i]...)
}
dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
start = i + 3
}
v := scan.step(scan, c)
if v >= scanSkipSpace {
if v == scanError {
break
}
if start < i {
dst = append(dst, src[start:i]...)
}
start = i + 1
}
}
if scan.eof() == scanError {
return dst[:origLen], scan.err
}
if start < len(src) {
dst = append(dst, src[start:]...)
}
return dst, nil
}

func appendNewline(dst []byte, prefix, indent string, depth int) []byte {
dst = append(dst, '\n')
dst = append(dst, prefix...)
Expand Down
67 changes: 0 additions & 67 deletions json/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,51 +55,6 @@ func TestValid(t *testing.T) {
}
}

func TestCompact(t *testing.T) {
tests := []struct {
CaseName
compact string
indent string
}{
{Name(""), `1`, `1`},
{Name(""), `{}`, `{}`},
{Name(""), `[]`, `[]`},
{Name(""), `{"":2}`, "{\n\t\"\": 2\n}"},
{Name(""), `[3]`, "[\n\t3\n]"},
{Name(""), `[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"},
{Name(""), `{"x":1}`, "{\n\t\"x\": 1\n}"},
{Name(""), `[true,false,null,"x",1,1.5,0,-5e+2]`, `[
true,
false,
null,
"x",
1,
1.5,
0,
-5e+2
]`},
{Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070
}
var buf bytes.Buffer
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
buf.Reset()
if err := Compact(&buf, []byte(tt.compact)); err != nil {
t.Errorf("%s: Compact error: %v", tt.Where, err)
} else if got := buf.String(); got != tt.compact {
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
}

buf.Reset()
if err := Compact(&buf, []byte(tt.indent)); err != nil {
t.Errorf("%s: Compact error: %v", tt.Where, err)
} else if got := buf.String(); got != tt.compact {
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
}
})
}
}

func TestIndent(t *testing.T) {
tests := []struct {
CaseName
Expand Down Expand Up @@ -150,28 +105,6 @@ func TestIndent(t *testing.T) {
}
}

func TestCompactSeparators(t *testing.T) {
// U+2028 and U+2029 should be escaped inside strings.
// They should not appear outside strings.
tests := []struct {
CaseName
in, compact string
}{
{Name(""), "{\"\u2028\": 1}", "{\"\u2028\":1}"},
{Name(""), "{\"\u2029\" :2}", "{\"\u2029\":2}"},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
var buf bytes.Buffer
if err := Compact(&buf, []byte(tt.in)); err != nil {
t.Errorf("%s: Compact error: %v", tt.Where, err)
} else if got := buf.String(); got != tt.compact {
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
}
})
}
}

func TestIndentErrors(t *testing.T) {
tests := []struct {
CaseName
Expand Down

0 comments on commit d0e4dee

Please sign in to comment.