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 }