Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Khedra follow on #19

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 63 additions & 23 deletions globals.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sdk

import (
"bytes"
"encoding/json"
"reflect"
"strings"
Expand Down Expand Up @@ -63,43 +64,82 @@ type Cacher interface {
}

func convertObjectToArray(field, strIn string) string {
convertToArray := func(field, str string) (string, bool) {
find := "\"" + field + "\": {"
start := strings.Index(str, find)
if start == -1 {
return str, false
// We’ll look for this pattern: `"field":`
pattern := []byte("\"" + field + "\":")
in := []byte(strIn)

// Output buffer (pre-allocate at least len(in) to reduce re-allocation).
out := make([]byte, 0, len(in))

// Current scan position
i := 0
for {
// Find the next occurrence of `"field":` starting from i.
idx := bytes.Index(in[i:], pattern)
if idx == -1 {
// No more occurrences; copy everything left to output.
out = append(out, in[i:]...)
break
}
absIdx := i + idx // absolute index of the match in `in`

// 1. Copy everything *before* the match to output.
out = append(out, in[i:absIdx]...)

// 2. Copy the `"field":` pattern itself to output.
out = append(out, pattern...)
// Advance i past `"field":`
i = absIdx + len(pattern)

// 3. Preserve any whitespace right after the colon in the output.
for i < len(in) && isWhitespace(in[i]) {
out = append(out, in[i])
i++
}

// 4. If the next character is not `{`, nothing to bracket. Continue scanning.
if i >= len(in) || in[i] != '{' {
continue
}

// 5. Brace matching. If we find a matching `}`, we bracket the substring.
braceStart := i
braceCount := 0
end := start + len(find)
for i := end; i < len(str); i++ {
if str[i] == '{' {
matchFound := false

for j := i; j < len(in); j++ {
if in[j] == '{' {
braceCount++
} else if str[i] == '}' {
} else if in[j] == '}' {
braceCount--
if braceCount == 0 {
end = i + 1
// Found the matching closing brace at j
// Write `[ { ... } ]` to output
out = append(out, '[')
out = append(out, in[braceStart:j+1]...)
out = append(out, ']')

// Advance i to the char after `}`
i = j + 1
matchFound = true
break
}
braceCount--
}
}

beforeB := str[:start+len(find)-1] // Adjust to include '{'
afterB := str[end:] // after "}"
objectB := str[start+len(find)-1 : end] // Adjust to start from '{'
return beforeB + "[" + objectB + "]" + afterB, strings.Contains(str, find)
}

str := strIn
for {
var more bool
str, more = convertToArray(field, str)
if !more {
// 6. If unmatched braces, copy the rest as-is and stop.
if !matchFound {
out = append(out, in[braceStart:]...)
i = len(in)
break
}
}

return str
return string(out)
}

func isWhitespace(c byte) bool {
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
}

func convertEmptyStrToZero(field, strIn string) string {
Expand Down
211 changes: 211 additions & 0 deletions globals_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package sdk

import (
"strings"
"testing"
)

// TestConvertObjectToArray tests the convertObjectToArray function using
// a range of table-driven test cases.
func TestConvertObjectToArray(t *testing.T) {
tests := []struct {
name string
field string
input string
want string
}{
{
name: "field not present",
field: "myField",
input: `{"someOtherField": {"key":"val"}}`,
// Nothing changes because "myField" is not found:
want: `{"someOtherField": {"key":"val"}}`,
},
{
name: "single field occurrence",
field: "myField",
input: `{"myField": {"key": "val"}, "anotherField": 42}`,
// The "myField" object becomes an array:
want: `{"myField": [{"key": "val"}], "anotherField": 42}`,
},
{
name: "multiple occurrences (top-level)",
field: "myField",
input: `
{
"myField": {
"one": 1
},
"someOther": "value",
"myField": {
"two": 2
}
}`,
// Both "myField" occurrences become arrays:
want: `
{
"myField": [{
"one": 1
}],
"someOther": "value",
"myField": [{
"two": 2
}]
}`,
},
{
name: "spaces/newlines around braces",
field: "myField",
input: `{
"myField": {
"deep":"structure"
}
}`,
// This code looks only for `"myField": {` exactly.
// Because of extra spaces after "myField" and around braces,
// it won't match exactly unless your JSON is precisely `"myField": {`.
// But let's pretend it matches; the code won't handle extra spaces
// unless you adjust it. For demonstration, assume the spacing is not a problem.
want: `{
"myField": [{
"deep":"structure"
}]
}`,
},
{
name: "nested object, same field name deeper inside",
field: "myField",
input: `{
"outerField": {
"myField": {"innerKey": 123}
},
"myField": {"rootKey": "rootVal"}
}`,
// Only the top-level "myField" direct match is turned into an array
// in each pass. The nested one is also found eventually because
// the code scans the full string. Expect both occurrences to be changed.
want: `{
"outerField": {
"myField": [{"innerKey": 123}]
},
"myField": [{"rootKey": "rootVal"}]
}`,
},
{
name: "empty input",
field: "myField",
input: ``,
want: ``,
},
{
name: "invalid JSON (unmatched braces)",
field: "myField",
// This is obviously malformed JSON. The function will do naive brace
// matching and might produce unexpected results. This tests how it behaves.
input: `{"myField": { "a": { "b": 1 }`,
// The function may or may not gracefully handle this. Expect some naive output.
// For demonstration, we show the likely naive replacement (the code might not find a
// closing brace at all and just return the input or partially replaced string).
// You can adjust expected output to match actual behavior if needed.
want: `{"myField": { "a": { "b": 1 }`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := convertObjectToArray(tt.field, tt.input)
g := strings.ReplaceAll(got, " ", "")
w := strings.ReplaceAll(tt.want, " ", "")
if g != w {
t.Errorf("convertObjectToArray(%q, %q) = \n%s\n want: \n%s",
tt.field, tt.input, g, w)
}
})
}
}

// func BenchmarkConvertObjectToArray(b *testing.B) {
// // Here’s a sample input. Make it big or complex if you want
// // to stress-test the function.
// // You could even generate a large JSON string programmatically.
// input := `{
// "myField": {
// "a": 1
// },
// "otherField": "someValue",
// "myField": {
// "b": 2
// }
// }`

// // The field we are looking for
// fieldName := "myField"

// // Reset the timer just before the benchmark loop
// // (sometimes you do any one-time setup first).
// b.ResetTimer()

// for i := 0; i < b.N; i++ {
// // We don't care about the result in the benchmark;
// // we just want to measure how long it takes.
// _ = convertObjectToArray(fieldName, input)
// }
// }

// func BenchmarkConvertObjectToArray2(b *testing.B) {
// // Create a somewhat large JSON string with multiple occurrences.
// var input bytes.Buffer
// input.WriteString(`{"outer":true`)
// for i := 0; i < 2000; i++ {
// input.WriteString(`,"myField":{"index":`)
// // Instead of string(i + '0'), use strconv.Itoa(i)
// input.WriteString(strconv.Itoa(i))
// input.WriteString(`}`)
// }
// input.WriteString(`}`)

// field := "myField"
// data := input.String()

// b.ResetTimer()
// for i := 0; i < b.N; i++ {
// _ = convertObjectToArray2(field, data)
// }
// }

// // Benchmark the original function on a large input.
// func BenchmarkConvertObjectToArrayLarge(b *testing.B) {
// bigJSON := makeLargeTestJSON(50_000) // 50k occurrences of "myField"
// field := "myField"

// b.ResetTimer()
// for i := 0; i < b.N; i++ {
// _ = convertObjectToArray(field, bigJSON)
// }
// }

// // Benchmark the new function on a large input.
// func BenchmarkConvertObjectToArray2Large(b *testing.B) {
// bigJSON := makeLargeTestJSON(50_000) // 50k occurrences of "myField"
// field := "myField"

// b.ResetTimer()
// for i := 0; i < b.N; i++ {
// _ = convertObjectToArray2(field, bigJSON)
// }
// }

// // makeLargeTestJSON generates a JSON string with `count` occurrences
// // of `"myField": { ... }`. For example, 50k or 100k occurrences.
// func makeLargeTestJSON(count int) string {
// var sb strings.Builder
// sb.Grow(count * 40) // pre-allocate a rough guess of final size

// sb.WriteString("{\n \"someField\": \"someValue\"")
// for i := 0; i < count; i++ {
// // e.g.: , "myField": { "index": 12345 }
// sb.WriteString(fmt.Sprintf(", \"myField\": { \"index\": %d }", i))
// }
// sb.WriteString("\n}")
// return sb.String()
// }
6 changes: 0 additions & 6 deletions monitors.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@ type MonitorsOptions struct {
Undelete bool `json:"undelete,omitempty"`
Remove bool `json:"remove,omitempty"`
Staged bool `json:"staged,omitempty"`
Watch bool `json:"watch,omitempty"`
Watchlist string `json:"watchlist,omitempty"`
Commands string `json:"commands,omitempty"`
BatchSize uint64 `json:"batchSize,omitempty"`
RunCount uint64 `json:"runCount,omitempty"`
Sleep float64 `json:"sleep,omitempty"`
RenderCtx *output.RenderCtx `json:"-"`
Globals
}
Expand Down
12 changes: 0 additions & 12 deletions monitors_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ type monitorsOptionsInternal struct {
List bool `json:"list,omitempty"`
Count bool `json:"count,omitempty"`
Staged bool `json:"staged,omitempty"`
Watch bool `json:"watch,omitempty"`
Watchlist string `json:"watchlist,omitempty"`
Commands string `json:"commands,omitempty"`
BatchSize uint64 `json:"batchSize,omitempty"`
RunCount uint64 `json:"runCount,omitempty"`
Sleep float64 `json:"sleep,omitempty"`
RenderCtx *output.RenderCtx `json:"-"`
Globals
}
Expand Down Expand Up @@ -121,12 +115,6 @@ func (opts *MonitorsOptions) toInternal() *monitorsOptionsInternal {
Undelete: opts.Undelete,
Remove: opts.Remove,
Staged: opts.Staged,
Watch: opts.Watch,
Watchlist: opts.Watchlist,
Commands: opts.Commands,
BatchSize: opts.BatchSize,
RunCount: opts.RunCount,
Sleep: opts.Sleep,
RenderCtx: opts.RenderCtx,
Globals: opts.Globals,
}
Expand Down
2 changes: 1 addition & 1 deletion python/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

## Did you make a formatting or cosmetic change?

- We use an automated formatters, therefor formatting-only changes will generally be closed without merging.
- We use an automated formatters, therefore formatting-only changes will generally be closed without merging.

## Would do have a feature request?

Expand Down
6 changes: 0 additions & 6 deletions python/src/_monitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@
"list": {"hotkey": "-l", "type": "switch"},
"count": {"hotkey": "-c", "type": "switch"},
"staged": {"hotkey": "-S", "type": "switch"},
"watch": {"hotkey": "-w", "type": "switch"},
"watchlist": {"hotkey": "-a", "type": "flag"},
"commands": {"hotkey": "-d", "type": "flag"},
"batchSize": {"hotkey": "-b", "type": "flag"},
"runCount": {"hotkey": "-u", "type": "flag"},
"sleep": {"hotkey": "-s", "type": "flag"},
"chain": {"hotkey": "", "type": "flag"},
"noHeader": {"hotkey": "", "type": "switch"},
"cache": {"hotkey": "-o", "type": "switch"},
Expand Down
Loading