Fix parser invalid results, add tests, readme
- Fix parser not producing the correct results - Add tests for everything - Add readme with example Ready for v1
This commit is contained in:
parent
dc24f39ca3
commit
4368f7713e
8 changed files with 199 additions and 14 deletions
44
README.md
Normal file
44
README.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# Treeificator
|
||||||
|
|
||||||
|

|
||||||
|
[](https://git.mstar.dev/mstar/treeificator/src/branch/main/LICENSE)
|
||||||
|
[](https://godocs.io/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 "<p>"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The suffix ending a block of type PInformer
|
||||||
|
func (p *PInformer) GetSuffix() string {
|
||||||
|
return "</p>"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
BIN
coverage_badge.png
Normal file
BIN
coverage_badge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
6
go.mod
6
go.mod
|
@ -2,11 +2,11 @@ module git.mstar.dev/mstar/treeificator
|
||||||
|
|
||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require git.mstar.dev/mstar/goutils v1.12.3
|
require git.mstar.dev/mstar/goutils v1.13.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
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
|
github.com/rs/zerolog v1.33.0 // indirect
|
||||||
golang.org/x/sys v0.12.0 // indirect
|
golang.org/x/sys v0.25.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
10
go.sum
10
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.13.0 h1:j2AA3izqTumZyUgC2wi/JdIZAtnDQLve2iexI5kWMgM=
|
||||||
git.mstar.dev/mstar/goutils v1.12.3/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA=
|
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/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/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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
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.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.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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
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 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
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.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.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.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=
|
||||||
|
|
17
informer_test.go
Normal file
17
informer_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
53
node_test.go
Normal file
53
node_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
17
parser.go
17
parser.go
|
@ -32,16 +32,18 @@ func marshal(
|
||||||
activeInformer Informer,
|
activeInformer Informer,
|
||||||
) ([]NodeElement, uint64) {
|
) ([]NodeElement, uint64) {
|
||||||
elements := []NodeElement{}
|
elements := []NodeElement{}
|
||||||
buffer := make([]rune, len(raw))
|
buffer := make([]rune, 0)
|
||||||
var consumed uint64 = 0
|
var consumed uint64 = 0
|
||||||
var insideDiff uint64 = 0 // Nr of runes consumed by recursive calls
|
var insideDiff uint64 = 0 // Nr of runes consumed by recursive calls
|
||||||
|
outer:
|
||||||
for i, char := range []rune(raw) {
|
for i, char := range []rune(raw) {
|
||||||
|
i++
|
||||||
// Skip runes that have been consumed by recursive calls
|
// Skip runes that have been consumed by recursive calls
|
||||||
if uint64(i)+insideDiff < consumed {
|
if uint64(i) < consumed {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
buffer = append(buffer, char)
|
buffer = append(buffer, char)
|
||||||
consumed = uint64(i) + insideDiff
|
consumed = uint64(i)
|
||||||
for k, v := range informers {
|
for k, v := range informers {
|
||||||
if !strings.HasSuffix(string(buffer), k) {
|
if !strings.HasSuffix(string(buffer), k) {
|
||||||
continue
|
continue
|
||||||
|
@ -53,7 +55,7 @@ func marshal(
|
||||||
NodeElement{Text: other.IntoPointer(strings.TrimSuffix(string(buffer), k))},
|
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)
|
subElems, subConsumed := marshal(string([]rune(raw)[consumed:]), informers, v)
|
||||||
elements = append(elements, NodeElement{
|
elements = append(elements, NodeElement{
|
||||||
Node: &Node{
|
Node: &Node{
|
||||||
|
@ -63,20 +65,21 @@ func marshal(
|
||||||
})
|
})
|
||||||
insideDiff += subConsumed
|
insideDiff += subConsumed
|
||||||
consumed += subConsumed
|
consumed += subConsumed
|
||||||
continue
|
continue outer
|
||||||
}
|
}
|
||||||
if activeInformer != nil && strings.HasSuffix(string(buffer), activeInformer.GetSuffix()) {
|
if activeInformer != nil && strings.HasSuffix(string(buffer), activeInformer.GetSuffix()) {
|
||||||
if len(buffer) > 0 {
|
if len(buffer) > 0 {
|
||||||
|
b := strings.TrimSuffix(string(buffer), activeInformer.GetSuffix())
|
||||||
elements = append(
|
elements = append(
|
||||||
elements,
|
elements,
|
||||||
NodeElement{
|
NodeElement{
|
||||||
Text: other.IntoPointer(
|
Text: other.IntoPointer(
|
||||||
strings.TrimSuffix(string(buffer), activeInformer.GetSuffix()),
|
b,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return elements, uint64(consumed)
|
return elements, uint64(consumed) + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(buffer) > 0 {
|
if len(buffer) > 0 {
|
||||||
|
|
66
parser_test.go
Normal file
66
parser_test.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package treeificator
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
type testInformer struct{}
|
||||||
|
|
||||||
|
func (testinformer *testInformer) GetPrefix() string {
|
||||||
|
return "<p>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testinformer *testInformer) GetSuffix() string {
|
||||||
|
return "</p>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testinformer *testInformer) GetName() string {
|
||||||
|
return "tester"
|
||||||
|
}
|
||||||
|
|
||||||
|
const testString = "some<p>sitrn</p>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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue