go-hep.org/x/hep@v0.38.1/groot/rcmd/merge.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  	"fmt"
     9  	"log"
    10  	stdpath "path"
    11  
    12  	"go-hep.org/x/hep/groot"
    13  	"go-hep.org/x/hep/groot/rhist"
    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  // Merge merges all input fnames ROOT files into the output oname one.
    20  func Merge(oname string, fnames []string, verbose bool) error {
    21  	o, err := groot.Create(oname)
    22  	if err != nil {
    23  		return fmt.Errorf("could not create output ROOT file %q: %w", oname, err)
    24  	}
    25  	defer o.Close()
    26  
    27  	cmd := mergeCmd{verbose: verbose}
    28  	tsks, err := cmd.mergeTasksFrom(o, fnames[0])
    29  	if err != nil {
    30  		return fmt.Errorf("could not create merge tasks: %w", err)
    31  	}
    32  
    33  	for _, fname := range fnames[1:] {
    34  		err := cmd.process(tsks, fname)
    35  		if err != nil {
    36  			return fmt.Errorf("could not process ROOT file %q: %w", fname, err)
    37  		}
    38  	}
    39  
    40  	for i := range tsks {
    41  		tsk := &tsks[i]
    42  		err := tsk.close(o)
    43  		if err != nil {
    44  			return fmt.Errorf("could not close task %d (%s): %w", i, tsk.path(), err)
    45  		}
    46  	}
    47  
    48  	err = o.Close()
    49  	if err != nil {
    50  		return fmt.Errorf("could not close output ROOT file %q: %w", oname, err)
    51  	}
    52  
    53  	return nil
    54  }
    55  
    56  type mergeCmd struct {
    57  	verbose bool
    58  }
    59  
    60  func (mergeCmd) acceptObj(obj root.Object) bool {
    61  	switch obj.(type) {
    62  	case rtree.Tree:
    63  		// need to specially handle rtree.Tree.
    64  		// rtree.Tree does not implement root.Merger: only rtree.Writer does.
    65  		return true
    66  	case rhist.H1, rhist.H2:
    67  		return true
    68  	case root.Merger:
    69  		return true
    70  	default:
    71  		return false
    72  	}
    73  }
    74  
    75  func (cmd mergeCmd) process(tsks []task, fname string) error {
    76  	if cmd.verbose {
    77  		log.Printf("merging [%s]...", fname)
    78  	}
    79  
    80  	f, err := groot.Open(fname)
    81  	if err != nil {
    82  		return fmt.Errorf("could not open input ROOT file %q: %w", fname, err)
    83  	}
    84  	defer f.Close()
    85  
    86  	for i := range tsks {
    87  		tsk := &tsks[i]
    88  		err = tsk.merge(f)
    89  		if err != nil {
    90  			return fmt.Errorf("could not merge task %d (%s) for file %q: %w", i, tsk.path(), fname, err)
    91  		}
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  type task struct {
    98  	dir string
    99  	key string
   100  	obj root.Object
   101  
   102  	verbose bool
   103  }
   104  
   105  func (cmd *mergeCmd) mergeTasksFrom(o *riofs.File, fname string) ([]task, error) {
   106  	f, err := groot.Open(fname)
   107  	if err != nil {
   108  		return nil, fmt.Errorf("could not open input ROOT file %q: %w", fname, err)
   109  	}
   110  	defer f.Close()
   111  
   112  	// handle relative/absolute path
   113  	top := stdpath.Join(f.Name(), ".")
   114  
   115  	var tsks []task
   116  	err = riofs.Walk(f, func(path string, obj root.Object, err error) error {
   117  		if err != nil {
   118  			return err
   119  		}
   120  		name := path[len(top):]
   121  		if name == "" {
   122  			return nil
   123  		}
   124  
   125  		if _, ok := obj.(riofs.Directory); ok {
   126  			_, err := riofs.Dir(o).Mkdir(name)
   127  			if err != nil {
   128  				return fmt.Errorf("could not create dir %q in output ROOT file: %w", name, err)
   129  			}
   130  			if cmd.verbose {
   131  				log.Printf("selecting %q", name)
   132  			}
   133  			return nil
   134  		}
   135  
   136  		if !cmd.acceptObj(obj) {
   137  			return nil
   138  		}
   139  		if cmd.verbose {
   140  			log.Printf("selecting %q", name)
   141  		}
   142  
   143  		var (
   144  			dirName = stdpath.Dir(name)
   145  			objName = stdpath.Base(name)
   146  			dir     = riofs.Directory(o)
   147  		)
   148  
   149  		if dirName != "/" && dirName != "" {
   150  			obj, err := riofs.Dir(o).Get(dirName)
   151  			if err != nil {
   152  				return fmt.Errorf("could not get dir %q from output ROOT file: %w", dirName, err)
   153  			}
   154  			dir = obj.(riofs.Directory)
   155  		}
   156  
   157  		switch oo := obj.(type) {
   158  		case rtree.Tree:
   159  			w, err := rtree.NewWriter(dir, objName, rtree.WriteVarsFromTree(oo), rtree.WithTitle(oo.Title()))
   160  			if err != nil {
   161  				return fmt.Errorf("could not create output ROOT tree %q: %w", name, err)
   162  			}
   163  
   164  			r, err := rtree.NewReader(oo, nil)
   165  			if err != nil {
   166  				return fmt.Errorf(
   167  					"could not create input ROOT tree reader %q: %w",
   168  					name, err,
   169  				)
   170  			}
   171  			defer r.Close()
   172  
   173  			_, err = rtree.Copy(w, r)
   174  			if err != nil {
   175  				return fmt.Errorf("could not seed output ROOT tree %q: %w", name, err)
   176  			}
   177  			obj = w
   178  		}
   179  
   180  		tsks = append(tsks, task{
   181  			dir:     dirName,
   182  			key:     objName,
   183  			obj:     obj,
   184  			verbose: cmd.verbose,
   185  		})
   186  		return nil
   187  	})
   188  	if err != nil {
   189  		return nil, fmt.Errorf("could not inspect input ROOT file: %w", err)
   190  	}
   191  
   192  	if cmd.verbose {
   193  		log.Printf("merging [%s]...", fname)
   194  	}
   195  
   196  	return tsks, nil
   197  }
   198  
   199  func (tsk *task) path() string {
   200  	return stdpath.Join(tsk.dir, tsk.key)
   201  }
   202  
   203  func (tsk *task) merge(f *riofs.File) error {
   204  	name := tsk.path()
   205  	obj, err := riofs.Dir(f).Get(name)
   206  	if err != nil {
   207  		return fmt.Errorf("could not get %q: %w", name, err)
   208  	}
   209  
   210  	err = tsk.mergeObj(tsk.obj, obj)
   211  	if err != nil {
   212  		return fmt.Errorf("could not merge %q: %w", name, err)
   213  	}
   214  
   215  	return nil
   216  }
   217  
   218  func (tsk *task) close(f *riofs.File) error {
   219  	var err error
   220  	switch obj := tsk.obj.(type) {
   221  	case rtree.Writer:
   222  		err = obj.Close()
   223  	default:
   224  		err = riofs.Dir(f).Put(tsk.path(), tsk.obj)
   225  	}
   226  
   227  	if err != nil {
   228  		return fmt.Errorf("could not save %q (%T) to output ROOT file: %w", tsk.path(), tsk.obj, err)
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  func (tsk *task) mergeObj(dst, src root.Object) error {
   235  	var (
   236  		rdst = dst.Class()
   237  		rsrc = src.Class()
   238  	)
   239  	if rdst != rsrc {
   240  		return fmt.Errorf("types differ: dst=%T, src=%T", dst, src)
   241  	}
   242  
   243  	switch dst := dst.(type) {
   244  	case rhist.H2:
   245  		return tsk.mergeH2(dst, src.(rhist.H2))
   246  	case root.Merger:
   247  		return dst.ROOTMerge(src)
   248  	default:
   249  		return fmt.Errorf("could not find suitable merge-API for (dst=%T, src=%T)", dst, src)
   250  	}
   251  }
   252  
   253  func (tsk *task) mergeH2(dst, src rhist.H2) error {
   254  	panic("not implemented")
   255  }