diff --git a/README.md b/README.md new file mode 100644 index 0000000..a172936 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Treeificator + +![Go Coverage](https://git.mstar.dev/mstar/treeificator/raw/branch/main/coverage_badge.png) +[![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://git.mstar.dev/mstar/treeificator/src/branch/main/LICENSE) +[![Go Documentation](https://godocs.io/git.mstar.dev/mstar/treeificator?status.svg)](https://godocs.io/git.mstar.dev/mstar/treeificator) +[![GoDoc](https://pkg.go.dev/badge/git.mstar.dev/mstar/treeificator)](https://pkg.go.dev/git.mstar.dev/mstar/treeificator) + +A small module to turn a string into a tree of nodes based on a list of informers. + +## How to use + +First import the module `go get git.mstar.dev/mstar/treeificator` + +Then define the informers for the beginning and end of each node type you want + +```go +type PInformer struct{} + +// The prefix starting a block of type PInformer +func (p *PInformer) GetPrefix() string { + return "

" +} + +// The suffix ending a block of type PInformer +func (p *PInformer) GetSuffix() string { + return "

" +} + +// The name of PInformer +func (p *PInformer) GetName() string { + return "p-element" +} +``` + +And finally parse any string + +```go +import "git.mstar.dev/mstar/treeificator" + +treeRoot := treeificator.Marshal(yourString, &PInformer{}) +``` + +See [documentation](https://pkg.go.dev/git.mstar.dev/mstar/treeificator) +for more details diff --git a/coverage_badge.png b/coverage_badge.png new file mode 100644 index 0000000..22cc9fa Binary files /dev/null and b/coverage_badge.png differ diff --git a/go.mod b/go.mod index f39d795..b2aa0f0 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,11 @@ module git.mstar.dev/mstar/treeificator go 1.24.2 -require git.mstar.dev/mstar/goutils v1.12.3 +require git.mstar.dev/mstar/goutils v1.13.0 require ( github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/rs/zerolog v1.33.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.25.0 // indirect ) diff --git a/go.sum b/go.sum index 59add93..5bc9449 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,19 @@ -git.mstar.dev/mstar/goutils v1.12.3 h1:Wx7i8/a99Cp+Y/XcXgqQr0r9cSsJu7QkWBlKyprTH44= -git.mstar.dev/mstar/goutils v1.12.3/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA= +git.mstar.dev/mstar/goutils v1.13.0 h1:j2AA3izqTumZyUgC2wi/JdIZAtnDQLve2iexI5kWMgM= +git.mstar.dev/mstar/goutils v1.13.0/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/informer_test.go b/informer_test.go new file mode 100644 index 0000000..a328f37 --- /dev/null +++ b/informer_test.go @@ -0,0 +1,17 @@ +package treeificator + +import "testing" + +// It's a constants container, but coverage still wants it tested +func TestDefaultInformer(t *testing.T) { + c := DefaultInformer{} + if x := c.GetName(); x != "default" { + t.Fatalf("expected \"default\" as default informer name, got %v", x) + } + if x := c.GetPrefix(); x != "" { + t.Fatalf("expected empty string for default informer prefix, got %v", x) + } + if x := c.GetSuffix(); x != "" { + t.Fatalf("expected empty string for default informer suffix, got %v", x) + } +} diff --git a/node_test.go b/node_test.go new file mode 100644 index 0000000..5805cd8 --- /dev/null +++ b/node_test.go @@ -0,0 +1,53 @@ +package treeificator + +import ( + "testing" + + "git.mstar.dev/mstar/goutils/other" +) + +func TestNodeUnmarshalTwoRawText(t *testing.T) { + strNode := Node{ + NodeType: &DefaultInformer{}, + Elements: []NodeElement{ + NodeElement{ + Text: other.IntoPointer("foo"), + Node: nil, + }, + NodeElement{ + Text: other.IntoPointer("bar"), + Node: nil, + }, + }, + } + if res := strNode.Unmarshal(); res != "foobar" { + t.Fatalf("expected \"foobar\" from unmarshal, got %v", res) + } +} + +func TestNodeElementUnmarshalRawText(t *testing.T) { + node := NodeElement{ + Text: other.IntoPointer("foo"), + Node: nil, + } + if res := node.Unmarshal(); res != "foo" { + t.Fatalf("expected \"foo\", got %v", res) + } +} + +func TestNodeElementUnmarshalNode(t *testing.T) { + node := NodeElement{ + Text: nil, + Node: &Node{ + NodeType: &DefaultInformer{}, + Elements: []NodeElement{ + NodeElement{ + Text: other.IntoPointer("foo"), + }, + }, + }, + } + if res := node.Unmarshal(); res != "foo" { + t.Fatalf("expected \"foo\", got %v", res) + } +} diff --git a/parser.go b/parser.go index e882af6..d48a960 100644 --- a/parser.go +++ b/parser.go @@ -32,16 +32,18 @@ func marshal( activeInformer Informer, ) ([]NodeElement, uint64) { elements := []NodeElement{} - buffer := make([]rune, len(raw)) + buffer := make([]rune, 0) var consumed uint64 = 0 var insideDiff uint64 = 0 // Nr of runes consumed by recursive calls +outer: for i, char := range []rune(raw) { + i++ // Skip runes that have been consumed by recursive calls - if uint64(i)+insideDiff < consumed { + if uint64(i) < consumed { continue } buffer = append(buffer, char) - consumed = uint64(i) + insideDiff + consumed = uint64(i) for k, v := range informers { if !strings.HasSuffix(string(buffer), k) { continue @@ -53,7 +55,7 @@ func marshal( NodeElement{Text: other.IntoPointer(strings.TrimSuffix(string(buffer), k))}, ) } - buffer = make([]rune, uint64(len(raw))-consumed) + buffer = make([]rune, 0) subElems, subConsumed := marshal(string([]rune(raw)[consumed:]), informers, v) elements = append(elements, NodeElement{ Node: &Node{ @@ -63,20 +65,21 @@ func marshal( }) insideDiff += subConsumed consumed += subConsumed - continue + continue outer } if activeInformer != nil && strings.HasSuffix(string(buffer), activeInformer.GetSuffix()) { if len(buffer) > 0 { + b := strings.TrimSuffix(string(buffer), activeInformer.GetSuffix()) elements = append( elements, NodeElement{ Text: other.IntoPointer( - strings.TrimSuffix(string(buffer), activeInformer.GetSuffix()), + b, ), }, ) } - return elements, uint64(consumed) + return elements, uint64(consumed) + 1 } } if len(buffer) > 0 { diff --git a/parser_test.go b/parser_test.go new file mode 100644 index 0000000..ebea3d7 --- /dev/null +++ b/parser_test.go @@ -0,0 +1,66 @@ +package treeificator + +import "testing" + +type testInformer struct{} + +func (testinformer *testInformer) GetPrefix() string { + return "

" +} + +func (testinformer *testInformer) GetSuffix() string { + return "

" +} + +func (testinformer *testInformer) GetName() string { + return "tester" +} + +const testString = "some

sitrn

for testing" + +func TestMarshalOnlyRaw(t *testing.T) { + res := Marshal(testString) + if l := len(res.Elements); l != 1 { + t.Fatalf("Expected to have one element, got %v", l) + } + if res.NodeType.GetName() != "default" { + t.Fatalf("Expected root informer to be default, got %v instead", res.NodeType.GetName()) + } + elem := res.Elements[0] + if elem.Text == nil { + t.Fatalf("Expected one raw string element with test string, got nil") + } + if *elem.Text != testString { + t.Fatalf("Expected one raw string element with test string, got %#v", *elem.Text) + } +} + +func TestMarshalWithInformer(t *testing.T) { + res := Marshal(testString, &testInformer{}) + if len(res.Elements) != 3 { + t.Fatalf("Expected 3 elements (text, node, text), got %#v instead", res.Elements) + } + raw1 := res.Elements[0] + subElem := res.Elements[1] + raw2 := res.Elements[2] + if raw1.Text == nil || *raw1.Text != "some" { + t.Fatalf("Expected first element to be raw text \"some\", got %#v", raw1) + } + if raw2.Text == nil || *raw2.Text != "for testing" { + t.Fatalf("Expected last element to be raw text \"for testing\", got %#v", *raw2.Text) + } + if subElem.Node == nil { + t.Fatalf("Expected 2nd element to be a node, got %#v", subElem) + } + n := subElem.Node + if name := n.NodeType.GetName(); name != "tester" { + t.Fatalf("Expected 2nd element node type name to be \"tester\", got %#v", name) + } + if len(n.Elements) != 1 { + t.Fatalf("Expected 2nd element to have one element, got %#v", n.Elements) + } + ns := n.Elements[0] + if ns.Text == nil || *ns.Text != "sitrn" { + t.Fatalf("Expected 2nd element to have one raw text of \"sitrn\", got %#v", ns.Text) + } +}