161 lines
3.5 KiB
Go
161 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)
|
||
|
}
|