🔖 Released v0.1.0
Just parsing io.Reader to get Node tree
This commit is contained in:
parent
bc48d66ff4
commit
e00ceba8eb
9 changed files with 267 additions and 0 deletions
14
doc.go
Normal file
14
doc.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
Package tree is a subset of a class of languages called two dimensional
|
||||
languages.
|
||||
|
||||
One dimensional languages assume a one dimensional array of bits with a single
|
||||
read head moving in order.
|
||||
|
||||
Two dimensional languages break those assumptions. There may be multiple read
|
||||
heads that can move not just on the x axis but on the y axis as well.
|
||||
|
||||
Tree Notation is a middle ground that utilitizes ideas from the two dimensional
|
||||
language world using present day technology.
|
||||
*/
|
||||
package tree
|
0
go.sum
Normal file
0
go.sum
Normal file
3
testdata/html.tree
vendored
Normal file
3
testdata/html.tree
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
html
|
||||
body
|
||||
div おはようございます
|
3
testdata/math.tree
vendored
Normal file
3
testdata/math.tree
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
multiply
|
||||
add 1 1
|
||||
add 2 2
|
6
testdata/nursinos.tree
vendored
Normal file
6
testdata/nursinos.tree
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
title Nursinos Services Offerered
|
||||
paragraph Here is a list of home nursing services we offer
|
||||
table
|
||||
Service Price
|
||||
BloodPressureCheck $10
|
||||
TemperatureCheck $5
|
6
testdata/package.tree
vendored
Normal file
6
testdata/package.tree
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
name mypackage
|
||||
version 2.1.1
|
||||
description A package
|
||||
repository
|
||||
type git
|
||||
url git://github.com/username/mypackage
|
2
testdata/print.tree
vendored
Normal file
2
testdata/print.tree
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
if true
|
||||
print Hello world
|
113
tree.go
Normal file
113
tree.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package tree
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Node represent a single data structure.
|
||||
type Node struct {
|
||||
Parent *Node
|
||||
Children []*Node
|
||||
Value string
|
||||
}
|
||||
|
||||
const (
|
||||
// nodeBreakSymbol delimits nodes (lines).
|
||||
nodeBreakSymbol rune = '\n'
|
||||
|
||||
// wordBreakSymbol delimits words (cells).
|
||||
wordBreakSymbol rune = ' '
|
||||
|
||||
// edgeSymbol is used to indicate the parent/child relationship between
|
||||
// nodes.
|
||||
//
|
||||
// TODO(toby3d): allow clients to change this to '\t' for example.
|
||||
edgeSymbol rune = wordBreakSymbol
|
||||
)
|
||||
|
||||
// Parse parses the data into a Tree Notation nodes.
|
||||
//
|
||||
// Note what all documents are valid Tree Notation documents. Like binary
|
||||
// notation, there are no syntax errors in Tree Notation.
|
||||
func Parse(data io.Reader) (root *Node) {
|
||||
stack := make([]*Node, 0)
|
||||
|
||||
scanner := bufio.NewScanner(data)
|
||||
for scanner.Scan() {
|
||||
node := &Node{
|
||||
Parent: nil,
|
||||
Children: nil,
|
||||
Value: scanner.Text(),
|
||||
}
|
||||
|
||||
if root == nil {
|
||||
root = node
|
||||
stack = append(stack, root)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if lenIndent(node.Value) <= lenIndent(root.Value) && len(stack) > 0 {
|
||||
root = stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
}
|
||||
|
||||
if root.Children == nil {
|
||||
root.Children = make([]*Node, 0)
|
||||
}
|
||||
|
||||
node.Parent = root
|
||||
root.Children = append(root.Children, node)
|
||||
stack = append(stack, node)
|
||||
root = stack[len(stack)-1]
|
||||
}
|
||||
|
||||
if root == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for root.Parent != nil {
|
||||
root = root.Parent
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
// Satisfies the fmt.Stringer interface to print node line passed in as an
|
||||
// operand to any format that accepts a string, or to an unformatted printer
|
||||
// such as fmt.Print.
|
||||
func (n Node) String() string {
|
||||
return strings.TrimLeft(n.Value, string(edgeSymbol))
|
||||
}
|
||||
|
||||
// GoString satisfies the fmt.GoStringer interface to print values passed as an
|
||||
// operand to a %#v format.
|
||||
func (n Node) GoString() (result string) {
|
||||
result += n.Value
|
||||
|
||||
for i := range n.Children {
|
||||
result += string(nodeBreakSymbol)
|
||||
result += n.Children[i].GoString()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// lenIndent count egdeSymbol prefixes in line.
|
||||
//
|
||||
// Returns 0 if line starts from any word character.
|
||||
func lenIndent(v string) int {
|
||||
count := 0
|
||||
|
||||
for i := range v {
|
||||
if rune(v[i]) != edgeSymbol {
|
||||
break
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
120
tree_test.go
Normal file
120
tree_test.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
package tree_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"source.toby3d.me/toby3d/tree"
|
||||
)
|
||||
|
||||
//go:embed testdata/*
|
||||
var testData embed.FS
|
||||
|
||||
func Example() {
|
||||
file, err := testData.Open(filepath.Join(".", "testdata", "html.tree"))
|
||||
if err != nil {
|
||||
log.Fatalf("cannot open file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
tree := tree.Parse(file)
|
||||
fmt.Printf("%#v", tree)
|
||||
// Output:
|
||||
// html
|
||||
// body
|
||||
// div おはようございます
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//nolint: paralleltest // testName used as range value
|
||||
for testName, filePath := range map[string]string{
|
||||
"html": filepath.Join(".", "testdata", "html.tree"),
|
||||
"math": filepath.Join(".", "testdata", "math.tree"),
|
||||
"nursinos": filepath.Join(".", "testdata", "nursinos.tree"),
|
||||
"package": filepath.Join(".", "testdata", "package.tree"),
|
||||
"print": filepath.Join(".", "testdata", "print.tree"),
|
||||
} {
|
||||
testName, filePath := testName, filePath
|
||||
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
src, err := testData.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open testing file: %v", err)
|
||||
}
|
||||
|
||||
result := tree.Parse(bytes.NewReader(src))
|
||||
if string(src) == fmt.Sprintf("%#v", result) {
|
||||
return
|
||||
}
|
||||
|
||||
t.Fatalf(`'%#v' is not equal '%s'`, result, string(src))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNode_String(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node := &tree.Node{
|
||||
Parent: nil,
|
||||
Value: "print Hello world",
|
||||
Children: nil,
|
||||
}
|
||||
|
||||
const expResult string = "print Hello world"
|
||||
if node.String() == expResult {
|
||||
return
|
||||
}
|
||||
|
||||
t.Fatalf(`'%s' is not equal '%s'`, node.String(), expResult)
|
||||
}
|
||||
|
||||
func TestNode_GoString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := &tree.Node{
|
||||
Parent: nil,
|
||||
Children: make([]*tree.Node, 0),
|
||||
Value: "multiply",
|
||||
}
|
||||
root.Children = append(root.Children, []*tree.Node{{
|
||||
Parent: root,
|
||||
Children: nil,
|
||||
Value: " add 1 1",
|
||||
}, {
|
||||
Parent: root,
|
||||
Children: nil,
|
||||
Value: " add 2 2",
|
||||
}}...)
|
||||
|
||||
const expResult string = "multiply\n add 1 1\n add 2 2"
|
||||
if root.GoString() == expResult {
|
||||
return
|
||||
}
|
||||
|
||||
t.Fatalf(`'%s' is not equal '%s'`, root.GoString(), expResult)
|
||||
}
|
||||
|
||||
func BenchmarkParse(b *testing.B) {
|
||||
input := strings.NewReader("title Nursinos Services Offerered\n" +
|
||||
"paragraph Here is a list of home nursing services we offer\n" +
|
||||
"table\n" +
|
||||
" Service Price\n" +
|
||||
" BloodPressureCheck $10\n" +
|
||||
" TemperatureCheck $5")
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tree.Parse(input)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue