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  }