Logo ROOT  
Reference Guide
Loading...
Searching...
No Matches
rootmkdir.cxx
Go to the documentation of this file.
1/// \file rootmkdir.cxx
2///
3/// Command line tool to create directories in ROOT files
4///
5/// \author Giacomo Parolini <giacomo.parolini@cern.ch>
6/// \date 2026-03-06
7#include <ROOT/RLogger.hxx>
8
9#include "logging.hxx"
10#include "optparse.hxx"
11#include "RootObjTree.hxx"
12#include "RootObjTree.cxx"
13
14#include <TClass.h>
15#include <TClassRef.h>
16#include <TError.h>
17#include <TFile.h>
18#include <TROOT.h>
19#include <TSystem.h>
20
21#include <iostream>
22#include <memory>
23#include <string_view>
24#include <vector>
25
26using namespace ROOT::CmdLine;
27
28static const char *const kShortHelp = "usage: rootmkdir [-p|--parents] [-v|--verbose] FILE:path/to/dir [...]\n";
29static const char *const kLongHelp = R"(
30Add directories in ROOT files
31
32positional arguments:
33 FILE:path File(s) and path(s) of directories to create
34
35options:
36 -h, --help get this help
37 -p, --parents create parent directories if needed, no error if directory already exists
38 -v, --verbose be verbose (can be repeated for increased verbosity)
39
40Note that calling rootmkdir without a directory path can be used to create files (similar to the `touch` command).
41You can also simultaneously create files and directories in it if you pass the -p flag.
42
43examples:
44- rootmkdir example.root:dir
45 Add the directory 'dir' to the ROOT file 'example.root' (file must exists)
46
47- rootmkdir example.root:dir1/dir2
48 Add the directory 'dir2' in 'dir1' which exists in the ROOT file 'example.root'
49
50- rootmkdir -p example.root:dir1/dir2/dir3
51 Make parent directories of 'dir3' as needed, no error if target directory already exists
52
53- rootmkdir non_existent.root
54 Create an empty ROOT file named 'non_existent.root'
55
56- rootmkdir -p non_existent.root:dir1
57 Create a ROOT file named 'non_existent.root' and directory `dir1` in it
58)";
59
61 enum class EPrintUsage {
65 };
67 bool fCreateParents = false;
68 std::vector<std::string> fDstFileAndPaths;
69};
70
71static RootMkdirArgs ParseArgs(const char **args, int nArgs)
72{
74
75 RootMkdirArgs outArgs;
76
77 RCmdLineOpts opts;
78 opts.AddFlag({"-h", "--help"});
79 opts.AddFlag({"-p", "--parents"});
81
82 opts.Parse(args, nArgs);
83
84 for (const auto &err : opts.GetErrors()) {
85 std::cerr << err << "\n";
86 }
87 if (!opts.GetErrors().empty()) {
89 return outArgs;
90 }
91
92 if (opts.GetSwitch("help")) {
94 return outArgs;
95 }
96
97 SetLogVerbosity(opts.GetSwitch("v"));
98
99 outArgs.fCreateParents = opts.GetSwitch("parents");
100
101 outArgs.fDstFileAndPaths = opts.GetArgs();
102 if (outArgs.fDstFileAndPaths.size() < 1)
104
105 return outArgs;
106}
107
108static bool IsDirectory(const TKey &key)
109{
110 static const TClassRef dirClassRef("TDirectory");
111 const auto *dirClass = TClass::GetClass(key.GetClassName());
112 return dirClass && dirClass->InheritsFrom(dirClassRef);
113}
114
115static bool MakeDirectory(TFile &file, std::string_view dirPath, bool createParents)
116{
117 // Partially copy-pasted from RFile::PutUntyped
118
119 const auto tokens = ROOT::Split(dirPath, "/");
120 const auto FullPathUntil = [&tokens](auto idx) {
121 return ROOT::Join("/", std::span<const std::string>{tokens.data(), idx + 1});
122 };
123 TDirectory *dir = &file;
124 for (auto tokIdx = 0u; tokIdx < tokens.size() - 1; ++tokIdx) {
125 TKey *existing = dir->GetKey(tokens[tokIdx].c_str());
126 // 4 cases here:
127 // 1. subdirectory exists? -> no problem.
128 // 2. non-directory object exists with the name of the subdirectory? -> error.
129 // 3. subdirectory does not exist and we passed -p? -> create it
130 // 4. subdirectory does not exist and we didn't pass -p? -> error.
131 if (existing) {
132 if (!IsDirectory(*existing)) {
133 Err() << "error adding '" + std::string(dirPath) + "': path '" + FullPathUntil(tokIdx) +
134 "' is already taken by an object of type '" + existing->GetClassName() + "'\n";
135 return false;
136 }
137 dir = existing->ReadObject<TDirectory>();
138 } else if (createParents) {
139 dir = dir->mkdir(tokens[tokIdx].c_str(), "", false);
140 if (dir)
141 Info(1) << "created directory '" << file.GetName() << ':' << FullPathUntil(tokIdx) << "'\n";
142 } else {
143 Err() << "cannot create directory '" + std::string(dirPath) + "': parent directory '" + FullPathUntil(tokIdx) +
144 "' does not exist. If you want to create the entire hierarchy, use the -p flag.\n";
145 return false;
146 }
147
148 if (!dir)
149 return false;
150 }
151
152 const TKey *existing = dir->GetKey(tokens[tokens.size() - 1].c_str());
153 if (existing) {
154 if (!IsDirectory(*existing)) {
155 Err() << "error adding '" + std::string(dirPath) + "': path is already taken by an object of type '" +
156 existing->GetClassName() + "'\n";
157 return false;
158 } else if (!createParents) {
159 Err() << "error adding '" + std::string(dirPath) + "': a directory already exists at that path.\n";
160 return false;
161 }
162
163 // directory already exists and we passed -p.
164 return true;
165 }
166
167 auto newDir = dir->mkdir(tokens[tokens.size() - 1].c_str(), "", false);
168 if (newDir)
169 Info(1) << "created directory '" << file.GetName() << ':' << std::string(dirPath) << "'\n";
170
171 return newDir != nullptr;
172}
173
174static bool ValidateDirPath(std::string_view path)
175{
176 if (path.rfind(';') != std::string_view::npos) {
177 Err() << "cannot specify cycle for the directory to create.\n";
178 return false;
179 }
180 return true;
181}
182
183int main(int argc, char **argv)
184{
185 InitLog("rootmkdir");
186
187 // Parse arguments
188 auto args = ParseArgs(const_cast<const char **>(argv) + 1, argc - 1);
189 if (args.fPrintHelp != RootMkdirArgs::EPrintUsage::kNo) {
190 std::cerr << kShortHelp;
191 if (args.fPrintHelp == RootMkdirArgs::EPrintUsage::kLong) {
192 std::cerr << kLongHelp;
193 return 0;
194 }
195 return 1;
196 }
197
198 // Validate and split all arguments into filename + dirpath
199 std::vector<std::pair<std::string_view, std::string_view>> dstFilesAndPaths;
200 dstFilesAndPaths.reserve(args.fDstFileAndPaths.size());
201 for (const auto &src : args.fDstFileAndPaths) {
202 auto res = SplitIntoFileNameAndPattern(src);
203 if (!res) {
204 Err() << res.GetError()->GetReport() << "\n";
205 return 1;
206 }
207 auto fnameAndPath = res.Unwrap();
208 if (!ValidateDirPath(fnameAndPath.second))
209 return 1;
210 dstFilesAndPaths.push_back(fnameAndPath);
211 }
212
213 // Create the directories
214 bool errors = false;
215 for (const auto &[dstFname, dstPath] : dstFilesAndPaths) {
216 const std::string fname{dstFname};
217 const bool fileExists = gSystem->AccessPathName(fname.c_str(), kFileExists) == 0;
218 // If the file does not exist we attempt to create it in the following cases:
219 // 1. dstPath is empty
220 // 2. dstPath is non-empty and the user passed the -p flag.
221 if (!fileExists && !(dstPath.empty() || args.fCreateParents)) {
222 Err() << "cannot create directory '" << dstFname << ":" << dstPath
223 << "': file does not exist. Use the -p flag if you want to create the file alongside the directories.\n";
224 errors = true;
225 continue;
226 } else if (fileExists && dstPath.empty() && !args.fCreateParents) {
227 Err() << "cannot create file '" << fname << "': already exists.\n";
228 errors = true;
229 continue;
230 }
231
232 auto file = std::unique_ptr<TFile>(TFile::Open(fname.c_str(), "UPDATE"));
233 if (!file || file->IsZombie()) {
234 Err() << "failed to open '" << fname << "' for writing.\n";
235 errors = true;
236 continue;
237 } else if (!fileExists) {
238 Info(1) << "created file '" << fname << "'\n";
239 }
240
241 if (!dstPath.empty())
242 errors |= !MakeDirectory(*file, dstPath, args.fCreateParents);
243 }
244
245 return errors;
246}
void Info(const char *location, const char *msgfmt,...)
Use this function for informational messages.
Definition TError.cxx:241
Double_t err
@ kFileExists
Definition TSystem.h:52
externTSystem * gSystem
Definition TSystem.h:582
std::vector< double > errors
void AddFlag(std::initializer_list< std::string_view > aliases, EFlagType type=EFlagType::kSwitch, std::string_view help="", std::uint32_t flagOpts=0)
Defines a new flag (either a switch or a flag with argument).
Definition optparse.hxx:208
const std::vector< std::string > & GetErrors() const
Returns all parsing errors.
Definition optparse.hxx:180
int GetSwitch(std::string_view name) const
If name refers to a previously-defined switch (i.e.
Definition optparse.hxx:300
const std::vector< std::string > & GetArgs() const
Retrieves all positional arguments.
Definition optparse.hxx:182
void Parse(const char **args, std::size_t nArgs)
Definition optparse.hxx:425
@ kFlagAllowMultiple
Flag is allowed to appear multiple times (default: it's an error to see the same flag twice).
Definition optparse.hxx:131
TClassRef is used to implement a permanent reference to a TClass object.
Definition TClassRef.h:29
static TClass * GetClass(const char *name, Bool_t load=kTRUE, Bool_t silent=kFALSE)
Static method returning pointer to TClass of the specified class name.
Definition TClass.cxx:2994
Describe directory structure in memory.
Definition TDirectory.h:45
A file, usually with extension .root, that stores data and code in the form of serialized objects in ...
Definition TFile.h:130
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
Create / open a file.
Definition TFile.cxx:3788
Book space in a file, create I/O buffers, to fill them, (un)compress them.
Definition TKey.h:28
T * ReadObject()
To read an object (non deriving from TObject) from the file.
Definition TKey.h:105
virtual const char * GetClassName() const
Definition TKey.h:77
void file()
Definition file.C:11
int main()
void SetLogVerbosity(int verbosity)
Definition logging.hxx:45
void InitLog(const char *name, int defaultVerbosity=1)
Definition logging.hxx:39
std::ostream & Err()
Definition logging.hxx:55
ROOT::RResult< std::pair< std::string_view, std::string_view > > SplitIntoFileNameAndPattern(std::string_view sourceRaw)
Given a string like "root://file.root:a/b/c", splits it into { "root://file.root",...
std::string Join(const std::string &sep, StringCollection_t &&strings)
Concatenate a list of strings with a separator.
std::vector< std::string > Split(std::string_view str, std::string_view delims, bool skipEmpty=false)
Splits a string at each character in delims.
void dir(char *path=0)
Definition rootalias.C:42
static const char *const kShortHelp
Command line tool to open a ROOT file on a TBrowser.
static const char *const kLongHelp
static bool MakeDirectory(TFile &file, std::string_view dirPath, bool createParents)
static bool IsDirectory(const TKey &key)
static bool ValidateDirPath(std::string_view path)
static RootMkdirArgs ParseArgs(const char **args, int nArgs)
Definition rootmkdir.cxx:71
EPrintUsage fPrintHelp
Definition rootmkdir.cxx:66
std::vector< std::string > fDstFileAndPaths
Definition rootmkdir.cxx:68
bool fCreateParents
Definition rootmkdir.cxx:67