From 5f0b90109aa9eafad5445685f441cb79c9a33fd4 Mon Sep 17 00:00:00 2001 From: Joe Higton Date: Sat, 18 Oct 2025 12:46:59 +0100 Subject: [PATCH 1/2] codeberg-ification! --- Readme.org | 4 ++-- filemode.go | 3 +-- filemode_test.go | 3 +-- go.mod | 2 +- json2nd.go | 3 +-- processor.go | 10 +++------- processor_test.go | 7 ++----- 7 files changed, 11 insertions(+), 21 deletions(-) diff --git a/Readme.org b/Readme.org index 503bad9..fd5c23a 100644 --- a/Readme.org +++ b/Readme.org @@ -39,12 +39,12 @@ For more see [[./doc/other_usage.org][other usage scenarios]], and [[./doc/json_ Assuming your ~$GOBIN~ directory is somewhere in your ~$PATH~ it's as simple as: #+begin_src sh - go install github.com/draxil/json2nd@latest + go install codeberg.org/draxil/json2nd@latest #+end_src ** Github releases -There are builds of release points on github. Grab the relevent build from [[https://github.com/draxil/json2nd/releases][the github releases]] page, right now these just contain a binary and docs. +There are builds of release points on github (but soon codeberg). Grab the relevent build from [[https://github.com/draxil/json2nd/releases][the github releases]] page, right now these just contain a binary and docs. * Plans / what about XYZ? diff --git a/filemode.go b/filemode.go index 570417c..fcc3c7e 100644 --- a/filemode.go +++ b/filemode.go @@ -5,11 +5,10 @@ import ( "io" "os" - "github.com/draxil/json2nd/internal/options" + "codeberg.org/draxil/json2nd/internal/options" ) func filemode(files []string, out io.Writer, opts options.Set) error { - for _, name := range files { f, err := os.Open(name) if err != nil { diff --git a/filemode_test.go b/filemode_test.go index 2abfec5..24baf49 100644 --- a/filemode_test.go +++ b/filemode_test.go @@ -5,12 +5,11 @@ import ( "errors" "testing" - "github.com/draxil/json2nd/internal/options" + "codeberg.org/draxil/json2nd/internal/options" "github.com/stretchr/testify/assert" ) func TestFileMode(t *testing.T) { - cases := []struct { name string files []string diff --git a/go.mod b/go.mod index 5e70094..7228b69 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/draxil/json2nd +module codeberg.org/draxil/json2nd go 1.16 diff --git a/json2nd.go b/json2nd.go index 0917203..5c2d143 100644 --- a/json2nd.go +++ b/json2nd.go @@ -6,13 +6,12 @@ import ( "os" "runtime/debug" - "github.com/draxil/json2nd/internal/options" + "codeberg.org/draxil/json2nd/internal/options" ) var version = "" func main() { - oh, err := options.New(os.Args[1:]) if err != nil { if err == flag.ErrHelp { diff --git a/processor.go b/processor.go index 0984b5e..01559bf 100644 --- a/processor.go +++ b/processor.go @@ -6,8 +6,8 @@ import ( "io" "strings" - "github.com/draxil/json2nd/internal/json" - "github.com/draxil/json2nd/internal/options" + "codeberg.org/draxil/json2nd/internal/json" + "codeberg.org/draxil/json2nd/internal/options" ) type processor struct { @@ -25,7 +25,6 @@ type processor struct { // TODO: path to value? func (p processor) run() error { - if p.in == nil { return errNilInput() } @@ -56,7 +55,6 @@ func (p processor) handlePath(scan *json.JSON) error { } func (p processor) handlePathNodes(nodes []string, scan *json.JSON) error { - // shouldn't be possible? But never say never. if len(nodes) == 0 { return fmt.Errorf("novel error 1: please report") @@ -101,7 +99,6 @@ func (p processor) prepOut() (w io.Writer, finishOut func() error) { } func (p processor) handleArray(js *json.JSON) error { - // shift the cursor from the start of the array: js.MoveOff() @@ -119,7 +116,6 @@ func (p processor) handleArray(js *json.JSON) error { } n, err := js.WriteCurrentTo(out, true) - if err != nil { return arrayJSONErr(err) } @@ -202,7 +198,6 @@ func (p processor) handleNonArray(j *json.JSON, clue byte, topLevel bool) error } func guessJSONType(clue byte) string { - switch clue { case '{': return "object" @@ -247,6 +242,7 @@ func errBadPath(chunk string) error { func errBlankPath() error { return fmt.Errorf("bad blank path node, did you have a double dot?") } + func errPathLeadToBadValue(start byte, path string) error { t := guessJSONType(start) diff --git a/processor_test.go b/processor_test.go index ecfd16b..a2c0eb6 100644 --- a/processor_test.go +++ b/processor_test.go @@ -7,13 +7,12 @@ import ( "strings" "testing" - "github.com/draxil/json2nd/internal/json" - "github.com/draxil/json2nd/internal/options" + "codeberg.org/draxil/json2nd/internal/json" + "codeberg.org/draxil/json2nd/internal/options" "github.com/stretchr/testify/assert" ) func TestProcessor(t *testing.T) { - cases := []struct { name string in io.Reader @@ -422,7 +421,6 @@ func TestProcessor(t *testing.T) { } func TestGuessJsonType(t *testing.T) { - cases := []struct { in byte exp string @@ -449,7 +447,6 @@ func TestGuessJsonType(t *testing.T) { } func TestErrPathLeadToBadValueMessage(t *testing.T) { - cases := []struct { name string clue byte From d115b19140be6c98cc081f26e9a3e4d569f0c231 Mon Sep 17 00:00:00 2001 From: Joe Higton Date: Thu, 19 Feb 2026 12:09:23 +0100 Subject: [PATCH 2/2] Fix for github#3 : escaped backslash at the end of a string Processing the following JSON fails: [ { "value": "abc\" } ] If the escaped backslash is followd by another character, it nevertheless works: [ { "value": "abc\d" } ] Co-authored-by: Joe Higton Co-committed-by: Joe Higton --- internal/json/json_test.go | 11 +++++++---- internal/json/scanner.go | 15 +++++++++++---- internal/json/scanner_test.go | 10 ++++++++-- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/internal/json/json_test.go b/internal/json/json_test.go index c3d71c0..e8dd103 100644 --- a/internal/json/json_test.go +++ b/internal/json/json_test.go @@ -23,7 +23,6 @@ func sread(s string) io.Reader { } func TestNext(t *testing.T) { - cases := []struct { name string reader io.Reader @@ -129,7 +128,6 @@ func TestWriteToByteChunkNoDelims(t *testing.T) { } func TestCurrentWriteTo(t *testing.T) { - cases := []struct { name string in io.Reader @@ -324,6 +322,13 @@ func TestCurrentWriteTo(t *testing.T) { assert.Equal(t, io.EOF, e) }, }, + { + name: "github issue #3", + in: sread(`[{"value":"abc\\"}]`), + delims: false, + exp: `{"value":"abc\\"}`, + expClue: '[', + }, } for _, tc := range cases { @@ -349,7 +354,6 @@ func TestCurrentWriteTo(t *testing.T) { } func TestScanForKey(t *testing.T) { - cases := []struct { name string reader io.Reader @@ -424,7 +428,6 @@ func TestScanForKeyValueSimple(t *testing.T) { } func TestSaneValueStart(t *testing.T) { - cases := []struct { in byte exp bool diff --git a/internal/json/scanner.go b/internal/json/scanner.go index 7a5d733..0bcba85 100644 --- a/internal/json/scanner.go +++ b/internal/json/scanner.go @@ -11,6 +11,7 @@ type state struct { last byte lastNotWs byte inStr bool + escape bool key bool open bool seek struct { @@ -23,7 +24,6 @@ type state struct { } func NewScanState(in byte) *state { - var inStr bool if in == '"' { inStr = true @@ -45,7 +45,6 @@ func (s *state) seekFor(key string) { } func (s *state) scan(chunk []byte, idx, max int) (int, error) { - if s.closer == 0 { return 0, ErrBadJSONValue{s.in} } @@ -66,9 +65,15 @@ func (s *state) scan(chunk []byte, idx, max int) (int, error) { } } + if b == '\\' && s.inStr && !s.escape { + s.escape = true + continue + } + // start or end of a string: if b == '"' { - if s.inStr && s.last != '\\' { + if s.inStr && !s.escape { + // end of string. s.inStr = false if s.in == '{' && s.key { @@ -78,6 +83,7 @@ func (s *state) scan(chunk []byte, idx, max int) (int, error) { if s.seek.matching && s.seek.cursor == len(s.seek.keyName) { s.seeking = false s.seekFound = true + s.escape = false return idx, nil } } @@ -87,6 +93,7 @@ func (s *state) scan(chunk []byte, idx, max int) (int, error) { if s.closer == '"' { s.last = b s.open = false + s.escape = false return idx, nil } } else if !s.inStr { @@ -122,7 +129,6 @@ func (s *state) scan(chunk []byte, idx, max int) (int, error) { } else { s.closerBalance-- } - } else if b == s.in { s.closerBalance++ } @@ -130,6 +136,7 @@ func (s *state) scan(chunk []byte, idx, max int) (int, error) { s.last = b s.lastNotWs = b + s.escape = false } return max, nil diff --git a/internal/json/scanner_test.go b/internal/json/scanner_test.go index 9ea9c70..019f738 100644 --- a/internal/json/scanner_test.go +++ b/internal/json/scanner_test.go @@ -69,7 +69,6 @@ func TestScanCloseBeforeEnd(t *testing.T) { pos, _ := s.scan(buf, 0, len(buf)) assert.False(t, s.open) assert.Equal(t, buf[pos], byte('}'), "cursor ends where we expect") - } func TestScanStringDoesNotCloseObject(t *testing.T) { @@ -97,6 +96,14 @@ func TestEscapedSubStringDoesNotClose(t *testing.T) { assert.False(t, s.open) } +func TestGithubNumberThree(t *testing.T) { + s := NewScanState('"') + buf := []byte(`\\"`) + _, err := s.scan(buf, 0, len(buf)) + assert.NoError(t, err) + assert.False(t, s.open) +} + func TestScanOnChar(t *testing.T) { s := NewScanState('Z') buf := []byte(`x\"`) @@ -117,7 +124,6 @@ func TestScanForSimple(t *testing.T) { _, err = s.scan(buf, 0, len(buf)) assert.NoError(t, err) assert.True(t, s.seekFound, "found") - } func TestScanForSimpleWithNestedTrap(t *testing.T) {