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) }