go-hep.org/x/hep@v0.38.1/groot/rcmd/ls.go (about) 1 // Copyright ©2020 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 "bytes" 9 "fmt" 10 "io" 11 "text/tabwriter" 12 13 "go-hep.org/x/hep/groot" 14 "go-hep.org/x/hep/groot/riofs" 15 "go-hep.org/x/hep/groot/root" 16 "go-hep.org/x/hep/groot/rtree" 17 ) 18 19 // ListOption controls how List behaves. 20 type ListOption func(*lsCmd) 21 22 type lsCmd struct { 23 w io.Writer 24 25 streamers bool 26 trees bool 27 } 28 29 // ListStreamers enables the display of streamer informations 30 // contained in the provided ROOT file. 31 func ListStreamers(v bool) ListOption { 32 return func(cmd *lsCmd) { 33 cmd.streamers = v 34 } 35 } 36 37 // ListTrees enables the detailed display of trees contained in 38 // the provided ROOT file. 39 func ListTrees(v bool) ListOption { 40 return func(cmd *lsCmd) { 41 cmd.trees = v 42 } 43 } 44 45 // List displays the summary content of the named ROOT file into the 46 // provided io Writer. 47 // 48 // List's behaviour can be customized with a set of optional ListOptions. 49 func List(w io.Writer, fname string, opts ...ListOption) error { 50 cmd := lsCmd{ 51 w: w, 52 streamers: false, 53 trees: false, 54 } 55 56 for _, opt := range opts { 57 opt(&cmd) 58 } 59 60 return cmd.ls(fname) 61 } 62 63 func (ls lsCmd) ls(fname string) error { 64 fmt.Fprintf(ls.w, "=== [%s] ===\n", fname) 65 f, err := groot.Open(fname) 66 if err != nil { 67 return fmt.Errorf("could not open file: %w", err) 68 } 69 defer f.Close() 70 71 fmt.Fprintf(ls.w, "version: %v\n", f.Version()) 72 if ls.streamers { 73 fmt.Fprintf(ls.w, "streamer-infos:\n") 74 sinfos := f.StreamerInfos() 75 for _, v := range sinfos { 76 name := v.Name() 77 fmt.Fprintf(ls.w, " StreamerInfo for %q version=%d title=%q\n", name, v.ClassVersion(), v.Title()) 78 w := tabwriter.NewWriter(ls.w, 8, 4, 1, ' ', 0) 79 for _, elm := range v.Elements() { 80 fmt.Fprintf(w, " %s\t%s\toffset=%3d\ttype=%3d\tsize=%3d\t %s\n", elm.TypeName(), elm.Name(), elm.Offset(), elm.Type(), elm.Size(), elm.Title()) 81 } 82 w.Flush() 83 } 84 fmt.Fprintf(ls.w, "---\n") 85 } 86 87 w := tabwriter.NewWriter(ls.w, 8, 4, 1, ' ', 0) 88 for _, k := range f.Keys() { 89 ls.walk(w, &k) 90 } 91 w.Flush() 92 93 return nil 94 } 95 96 type keyer interface { 97 ClassName() string 98 Name() string 99 Title() string 100 Cycle() int 101 Value() any 102 } 103 104 func (ls lsCmd) walk(w io.Writer, k keyer) { 105 if ls.trees && isTreelike(k.ClassName()) { 106 obj := k.Value() 107 tree, ok := obj.(rtree.Tree) 108 if ok { 109 w := newWindent(2, w) 110 fmt.Fprintf(w, "%s\t%s\t%s\t(entries=%d)\n", k.ClassName(), k.Name(), k.Title(), tree.Entries()) 111 displayBranches(w, tree, 2) 112 w.Flush() 113 return 114 } 115 } 116 fmt.Fprintf(w, "%s\t%s\t%s\t(cycle=%d)\n", k.ClassName(), k.Name(), k.Title(), k.Cycle()) 117 switch { 118 case isDirlike(k.ClassName()): 119 obj := k.Value() 120 if dir, ok := obj.(riofs.Directory); ok { 121 w := newWindent(2, w) 122 for _, k := range dir.Keys() { 123 ls.walk(w, &k) 124 } 125 w.Flush() 126 } 127 case isObjFinder(k.Value().(root.Object)): 128 v := k.Value() 129 if obj, ok := v.(root.ObjectFinder); ok { 130 keys := obj.Keys() 131 if len(keys) == 0 { 132 return 133 } 134 w := newWindent(2, w) 135 for _, k := range keys { 136 o, err := obj.Get(k) 137 if err != nil { 138 panic(err) 139 } 140 ls.walk(w, keyObj{k, o}) 141 } 142 w.Flush() 143 } 144 } 145 } 146 147 func isDirlike(class string) bool { 148 switch class { 149 case "TDirectory", "TDirectoryFile": 150 return true 151 } 152 return false 153 } 154 155 func isTreelike(class string) bool { 156 switch class { 157 case "TTree", "TTreeSQL", "TChain", "TNtuple", "TNtupleD": 158 return true 159 } 160 return false 161 } 162 163 func isObjFinder(o root.Object) bool { 164 _, ok := o.(root.ObjectFinder) 165 return ok 166 } 167 168 type windent struct { 169 hdr []byte 170 w io.Writer 171 } 172 173 func newWindent(n int, w io.Writer) *windent { 174 return &windent{ 175 hdr: bytes.Repeat([]byte(" "), n), 176 w: w, 177 } 178 } 179 180 func (w *windent) Write(data []byte) (int, error) { 181 return w.w.Write(append(w.hdr, data...)) 182 } 183 184 func (w *windent) Flush() error { 185 ww, ok := w.w.(interface{ Flush() error }) 186 if !ok { 187 return nil 188 } 189 return ww.Flush() 190 } 191 192 type brancher interface { 193 Branches() []rtree.Branch 194 } 195 196 func displayBranches(w io.Writer, bres brancher, indent int) { 197 branches := bres.Branches() 198 if len(branches) <= 0 { 199 return 200 } 201 ww := newWindent(indent, w) 202 for _, b := range branches { 203 var ( 204 name = clip(b.Name(), 60) 205 title = clip(b.Title(), 50) 206 class = clip(b.Class(), 20) 207 ) 208 fmt.Fprintf(ww, "%s\t%q\t%v\n", name, title, class) 209 displayBranches(ww, b, 2) 210 } 211 ww.Flush() 212 } 213 214 func clip(s string, n int) string { 215 if len(s) > n { 216 s = s[:n-5] + "[...]" 217 } 218 return s 219 } 220 221 type keyObj struct { 222 name string 223 obj root.Object 224 } 225 226 var _ keyer = (*keyObj)(nil) 227 228 func (k keyObj) ClassName() string { 229 return k.obj.Class() 230 } 231 232 func (k keyObj) Name() string { 233 return k.name 234 } 235 236 func (k keyObj) Title() string { 237 if n, ok := k.obj.(root.Named); ok { 238 return n.Title() 239 } 240 return "" 241 } 242 243 func (k keyObj) Cycle() int { 244 return 0 245 } 246 247 func (k keyObj) Value() any { 248 return k.obj 249 }