🔖 Released v0.1.0
Just parsing io.Reader to get Node tree
This commit is contained in:
parent
bc48d66ff4
commit
e00ceba8eb
|
@ -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,0 +1,3 @@
|
||||||
|
html
|
||||||
|
body
|
||||||
|
div おはようございます
|
|
@ -0,0 +1,3 @@
|
||||||
|
multiply
|
||||||
|
add 1 1
|
||||||
|
add 2 2
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
||||||
|
name mypackage
|
||||||
|
version 2.1.1
|
||||||
|
description A package
|
||||||
|
repository
|
||||||
|
type git
|
||||||
|
url git://github.com/username/mypackage
|
|
@ -0,0 +1,2 @@
|
||||||
|
if true
|
||||||
|
print Hello world
|
|
@ -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
|
||||||
|
}
|
|
@ -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 New Issue