🔖 Released v0.1.0

Just parsing io.Reader to get Node tree
This commit is contained in:
Maxim Lebedev 2022-01-06 23:55:55 +05:00
parent bc48d66ff4
commit e00ceba8eb
9 changed files with 267 additions and 0 deletions

14
doc.go Normal file
View 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
View File

3
testdata/html.tree vendored Normal file
View File

@ -0,0 +1,3 @@
html
body
div おはようございます

3
testdata/math.tree vendored Normal file
View File

@ -0,0 +1,3 @@
multiply
add 1 1
add 2 2

6
testdata/nursinos.tree vendored Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
if true
print Hello world

113
tree.go Normal file
View 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
View 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)
}
}