gotools/formatting/fractaltools.go
2018-12-19 14:45:37 +01:00

160 lines
3.5 KiB
Go

package formatting
import (
"encoding/json"
"fmt"
"sync"
)
type pathToDoc struct {
path string
doc string
}
// Fractal is fractal. It is an element of itself. It ships with a Mutex
// to handle concurrent reads and writes.
type Fractal struct {
Branches []*Fractal
Node string
Leaf string
Ctl sync.Mutex
}
func (t *Fractal) marshaler(ch chan string, chErr chan error) {
if t.Leaf != "" {
nodeJSON, err := json.Marshal(t.Node)
if err != nil {
chErr <- err
return
}
leafJSON, err := json.Marshal(t.Leaf)
if err != nil {
chErr <- err
return
}
ch <- fmt.Sprintf("%s: %s", nodeJSON, leafJSON)
return
}
nodeJSON, err := json.Marshal(t.Node)
if err != nil {
chErr <- err
return
}
res := fmt.Sprintf("%s: {", nodeJSON)
subch := make(chan string)
for _, branch := range t.Branches {
go branch.marshaler(subch, chErr)
}
limit := len(t.Branches)
for limit > 0 {
select {
case part := <-subch:
if limit > 1 {
res = fmt.Sprintf("%s%s, ", res, part)
} else {
res = fmt.Sprintf("%s%s}", res, part)
}
limit--
}
}
ch <- res
return
}
// MarshalJSON implements Marshal interface for the Fractal type
func (t *Fractal) MarshalJSON() ([]byte, error) {
var part string
res := "{"
ch := make(chan string)
chErr := make(chan error)
limit := 1
go t.marshaler(ch, chErr)
for limit != 0 {
select {
case part = <-ch:
if limit > 1 {
res = fmt.Sprintf("%s%s, ", res, part)
} else {
res = fmt.Sprintf("%s%s", res, part)
}
limit--
case err := <-chErr:
fmt.Println("[chErr] err:", err)
return nil, err
}
}
res = fmt.Sprintf("%s}", res)
return []byte(res), nil
}
// New initializes a new Fractal element
func (t *Fractal) New(node string) {
t.Ctl.Lock()
t.Node = node
t.Ctl.Unlock()
}
// Seal adds a Node and a Leaf to it and it should be considered closed
func (t *Fractal) Seal(node, leaf string) {
t.Ctl.Lock()
// no need to lock as it's a leaf node
var f Fractal
f.Node = node
f.Leaf = leaf
t.Branches = append(t.Branches, &f)
t.Ctl.Unlock()
}
// Extend is recursive and walks all the paths appending the nodes to
// the supplied fractal
func Extend(fract *Fractal, pathMap map[string][]pathToDoc, wg *sync.WaitGroup) {
for _, paths := range pathMap {
subPathMap := make(map[string][]pathToDoc)
for _, path := range paths {
subnode, subpath := splitPath(path)
if subpath.path == "" {
fract.Seal(subnode, subpath.doc)
} else {
subPathMap[subnode] = append(subPathMap[subnode], subpath)
}
}
if len(subPathMap) > 0 {
//fract.New(node)
for k := range subPathMap {
wg.Add(1)
var f Fractal
f.New(k)
fract.Branches = append(fract.Branches, &f)
tmp := make(map[string][]pathToDoc)
tmp[k] = subPathMap[k]
go Extend(&f, tmp, wg)
}
}
}
wg.Done()
}
// BuildJSON is the constructor of the fract, given a list of paths
func BuildJSON(pathsWithDocs []pathToDoc) *Fractal {
branches := make(map[string][]pathToDoc)
branches["/"] = pathsWithDocs
var fract Fractal
fract.New("/")
var w sync.WaitGroup
w.Add(1)
go Extend(&fract, branches, &w)
w.Wait()
return &fract
}
// EncodeJSON is the main function. Given a map with all the pairs
// path:doc and a json.Encoder it actually performs the encoding
func EncodeJSON(documentedPaths map[string]string, enc *json.Encoder) {
var pathsWithDocs []pathToDoc
for path, doc := range documentedPaths {
pathsWithDocs = append(pathsWithDocs, pathToDoc{trimSlashes(path), doc})
}
fract := BuildJSON(pathsWithDocs)
enc.Encode(fract)
}