go-hep.org/x/hep@v0.38.1/groot/rcmd/dump.go (about) 1 // Copyright ©2019 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package rcmd 6 7 import ( 8 "errors" 9 "fmt" 10 "io" 11 "reflect" 12 "strconv" 13 14 "go-hep.org/x/hep/groot" 15 "go-hep.org/x/hep/groot/rdict" 16 "go-hep.org/x/hep/groot/rhist" 17 "go-hep.org/x/hep/groot/riofs" 18 "go-hep.org/x/hep/groot/root" 19 "go-hep.org/x/hep/groot/rtree" 20 "go-hep.org/x/hep/hbook/rootcnv" 21 "go-hep.org/x/hep/hbook/yodacnv" 22 ) 23 24 // Dump dumps the content of the fname ROOT file to the provided io.Writer. 25 // If deep is true, Dump will recursively inspect directories and trees. 26 // Dump only display the content of ROOT objects satisfying the provided filter function. 27 // 28 // If filter is nil, Dump will consider all ROOT objects. 29 func Dump(w io.Writer, fname string, deep bool, filter func(name string) bool) error { 30 f, err := groot.Open(fname) 31 if err != nil { 32 return fmt.Errorf("could not open file with read-access: %w", err) 33 } 34 defer f.Close() 35 36 if filter == nil { 37 filter = func(string) bool { return true } 38 } 39 40 cmd := dumpCmd{ 41 w: w, 42 deep: deep, 43 match: filter, 44 } 45 return cmd.dumpDir(f) 46 } 47 48 type dumpCmd struct { 49 w io.Writer 50 deep bool 51 match func(name string) bool 52 } 53 54 func (cmd *dumpCmd) dumpDir(dir riofs.Directory) error { 55 for i, key := range dir.Keys() { 56 fmt.Fprintf(cmd.w, "key[%03d]: %s;%d %q (%s)", i, key.Name(), key.Cycle(), key.Title(), key.ClassName()) 57 if !(cmd.deep && cmd.match(key.Name())) { 58 fmt.Fprint(cmd.w, "\n") 59 continue 60 } 61 obj, err := key.Object() 62 if err != nil { 63 return fmt.Errorf("could not decode object %q from dir %q: %w", key.Name(), dir.(root.Named).Name(), err) 64 } 65 err = cmd.dumpObj(obj) 66 if errors.Is(err, errIgnoreKey) { 67 continue 68 } 69 if err != nil { 70 return fmt.Errorf("error dumping key %q: %w", key.Name(), err) 71 } 72 } 73 return nil 74 } 75 76 var errIgnoreKey = fmt.Errorf("rcmd: ignore key") 77 78 func (cmd *dumpCmd) dumpObj(obj root.Object) error { 79 var err error 80 switch obj := obj.(type) { 81 case rtree.Tree: 82 fmt.Fprintf(cmd.w, "\n") 83 err = cmd.dumpTree(obj) 84 case riofs.Directory: 85 fmt.Fprintf(cmd.w, "\n") 86 err = cmd.dumpDir(obj) 87 case rhist.H2: 88 fmt.Fprintf(cmd.w, "\n") 89 err = cmd.dumpH2(obj) 90 case rhist.H1: // keep after rhist.H2 91 fmt.Fprintf(cmd.w, "\n") 92 err = cmd.dumpH1(obj) 93 case rhist.Graph: 94 fmt.Fprintf(cmd.w, "\n") 95 err = cmd.dumpGraph(obj) 96 case rhist.MultiGraph: 97 for _, g := range obj.Graphs() { 98 fmt.Fprintf(cmd.w, "\n") 99 err = cmd.dumpGraph(g) 100 if err != nil { 101 return err 102 } 103 } 104 case root.List: 105 fmt.Fprintf(cmd.w, "\n") 106 err = cmd.dumpList(obj) 107 case *rdict.Object: 108 fmt.Fprintf(cmd.w, " => %v\n", obj) 109 case fmt.Stringer: 110 fmt.Fprintf(cmd.w, " => %q\n", obj.String()) 111 case root.ObjectFinder: 112 keys := obj.Keys() 113 if len(keys) == 0 { 114 return err 115 } 116 fmt.Fprintf(cmd.w, "\n") 117 for _, name := range keys { 118 sub, err := obj.Get(name) 119 if err != nil { 120 return err 121 } 122 err = cmd.dumpObj(sub) 123 if err != nil { 124 return err 125 } 126 } 127 default: 128 fmt.Fprintf(cmd.w, " => ignoring key of type %T\n", obj) 129 return errIgnoreKey 130 } 131 return err 132 } 133 134 func (cmd *dumpCmd) dumpList(lst root.List) error { 135 for i := range lst.Len() { 136 fmt.Fprintf(cmd.w, "lst[%s][%d]: ", lst.Name(), i) 137 err := cmd.dumpObj(lst.At(i)) 138 if err != nil && !errors.Is(err, errIgnoreKey) { 139 return fmt.Errorf("could not dump list: %w", err) 140 } 141 } 142 return nil 143 } 144 145 func (cmd *dumpCmd) dumpTree(t rtree.Tree) error { 146 vars := rtree.NewReadVars(t) 147 r, err := rtree.NewReader(t, vars) 148 if err != nil { 149 return fmt.Errorf("could not create reader: %w", err) 150 } 151 defer r.Close() 152 153 names := make([][]byte, len(vars)) 154 for i, v := range vars { 155 name := v.Name 156 if v.Leaf != "" && v.Leaf != v.Name { 157 name = v.Name + "." + v.Leaf 158 } 159 names[i] = []byte(name) 160 } 161 162 // FIXME(sbinet): don't use a "global" buffer for when rtree.Reader reads multiple 163 // events in parallel. 164 buf := make([]byte, 0, 8*1024) 165 hdr := make([]byte, 0, 6) 166 err = r.Read(func(rctx rtree.RCtx) error { 167 hdr = hdr[:0] 168 hdr = append(hdr, '[') 169 switch { 170 case rctx.Entry < 10: 171 hdr = append(hdr, '0', '0') 172 case rctx.Entry < 100: 173 hdr = append(hdr, '0') 174 } 175 hdr = strconv.AppendInt(hdr, rctx.Entry, 10) 176 hdr = append(hdr, ']', '[') 177 for i, v := range vars { 178 buf = buf[:0] 179 buf = append(buf, hdr...) 180 buf = append(buf, names[i]...) 181 buf = append(buf, ']', ':', ' ') 182 rv := reflect.Indirect(reflect.ValueOf(v.Value)) 183 // All of this is a convoluted (but efficient) way to do: 184 // fmt.Fprintf(cmd.w, "[%03d][%s]: %v\n", rctx.Entry, name, rv.Interface()) 185 buf = append(buf, fmt.Sprintf("%v\n", rv.Interface())...) 186 _, err = cmd.w.Write(buf) 187 if err != nil { 188 return err 189 } 190 } 191 return nil 192 }) 193 if err != nil { 194 return fmt.Errorf("rcmd: could not read through tree: %w", err) 195 } 196 return nil 197 } 198 199 func (cmd *dumpCmd) dumpH1(h1 rhist.H1) error { 200 h := rootcnv.H1D(h1) 201 return yodacnv.Write(cmd.w, h) 202 } 203 204 func (cmd *dumpCmd) dumpH2(h2 rhist.H2) error { 205 h := rootcnv.H2D(h2) 206 return yodacnv.Write(cmd.w, h) 207 } 208 209 func (cmd *dumpCmd) dumpGraph(gr rhist.Graph) error { 210 g := rootcnv.S2D(gr) 211 return yodacnv.Write(cmd.w, g) 212 }