From cd91a22cc8dc663e696674b9b455a0ea3f2afe12 Mon Sep 17 00:00:00 2001 From: silverweed Date: Thu, 21 Aug 2025 15:02:56 +0200 Subject: [PATCH 1/5] [main] Create C++ version of rootbrowse --- main/CMakeLists.txt | 3 + main/src/rootbrowse.cxx | 130 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 main/src/rootbrowse.cxx diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 73b535e140842..b8d0610bcb6d3 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -111,6 +111,9 @@ if(WIN32) set_property(TARGET rootcling APPEND_STRING PROPERTY LINK_FLAGS " -STACK:4000000") endif() +if(gui) + ROOT_EXECUTABLE(rootbrowse src/rootbrowse.cxx LIBRARIES RIO Core Rint Gui) +endif() ROOT_EXECUTABLE(rootls src/rootls.cxx LIBRARIES RIO Tree Core Rint ROOTNTuple) # Create aliases: rootcint, genreflex. diff --git a/main/src/rootbrowse.cxx b/main/src/rootbrowse.cxx new file mode 100644 index 0000000000000..b5608a2ae938b --- /dev/null +++ b/main/src/rootbrowse.cxx @@ -0,0 +1,130 @@ +// \file rootbrowse.cxx +/// +/// Command line tool to open a ROOT file on a TBrowser +/// +/// \author Giacomo Parolini +/// \date 2025-08-21 +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +static const char *const kLongHelp = R"( +Open a ROOT file in a TBrowser + +positional arguments: + FILE Input file + +options: + -h, --help show this help message and exit + -w, --web WEB Configure webdisplay. For all possible values, see: + https://root.cern/doc/v636/classTROOT.html#a1749472696545b76a6b8e79769e7e773 + -wf, --webOff Invoke the normal TBrowser (not the web version) + +Examples: +- rootbrowse + Open a TBrowser + +- rootbrowse file.root + Open the ROOT file 'file.root' in a TBrowser +)"; + +static ROOT::RLogChannel &RootBrowseLog() +{ + static ROOT::RLogChannel channel("RootBrowse"); + return channel; +} + +struct RootBrowseArgs { + enum class EPrintUsage { + kNo, + kShort, + kLong + }; + EPrintUsage fPrintHelp = EPrintUsage::kNo; + const char *fWeb = "on"; + const char *fFileName = nullptr; +}; + +static RootBrowseArgs ParseArgs(const char **args, int nArgs) +{ + RootBrowseArgs outArgs; + bool forcePositional = false; + + for (int i = 0; i < nArgs; ++i) { + const char *arg = args[i]; + bool isFlag = !forcePositional && arg[0] == '-'; + if (isFlag) { + if (strcmp(arg, "-w") == 0 || strcmp(arg, "--web") == 0) { + if (i < nArgs - 1 && args[i + 1][0] != '-') { + ++i; + outArgs.fWeb = args[i]; + } + } else if (strcmp(arg, "-wf") == 0 || strcmp(arg, "--webOff") == 0) { + outArgs.fWeb = "off"; + } else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) { + outArgs.fPrintHelp = RootBrowseArgs::EPrintUsage::kLong; + break; + } else if (strcmp(arg, "--") == 0) { + forcePositional = true; + } else { + outArgs.fPrintHelp = RootBrowseArgs::EPrintUsage::kShort; + break; + } + } else if (outArgs.fFileName) { + outArgs.fPrintHelp = RootBrowseArgs::EPrintUsage::kShort; + break; + } else { + outArgs.fFileName = arg; + } + } + + return outArgs; +} + +int main(int argc, char **argv) +{ + auto args = ParseArgs(const_cast(argv) + 1, argc - 1); + if (args.fPrintHelp != RootBrowseArgs::EPrintUsage::kNo) { + std::cerr << "usage: rootbrowse [-w WEB|-wf] \n"; + if (args.fPrintHelp == RootBrowseArgs::EPrintUsage::kLong) { + std::cerr << kLongHelp; + return 0; + } + return 1; + } + + gROOT->SetWebDisplay(args.fWeb); + + std::unique_ptr file; + if (args.fFileName) { + gErrorIgnoreLevel = kError; + file = std::unique_ptr(TFile::Open(args.fFileName, "READ")); + if (!file || file->IsZombie()) { + R__LOG_WARNING(RootBrowseLog()) << "File " << args.fFileName << " does not exist or is unreadable."; + } + gErrorIgnoreLevel = kUnset; + } + + // NOTE: we need to instantiate TApplication ourselves, otherwise TBrowser + // will create a batch application that cannot show graphics. + TApplication app("rootbrowse", nullptr, nullptr); + + auto browser = std::make_unique(); + std::cout << "Press ctrl+c to exit.\n"; + while (!gROOT->IsInterrupted() && !gSystem->ProcessEvents()) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + return 0; +} From 797cf934617a83bc2a6e435edf649dfedb1f9276 Mon Sep 17 00:00:00 2001 From: silverweed Date: Fri, 22 Aug 2025 09:18:38 +0200 Subject: [PATCH 2/5] [main] Remove rootbrowse.py --- core/base/CMakeLists.txt | 2 -- main/python/cmdLineUtils.py | 34 ----------------------- main/python/rootbrowse.py | 55 ------------------------------------- 3 files changed, 91 deletions(-) delete mode 100755 main/python/rootbrowse.py diff --git a/core/base/CMakeLists.txt b/core/base/CMakeLists.txt index dd0d067cc22d9..f2f9518eb3495 100644 --- a/core/base/CMakeLists.txt +++ b/core/base/CMakeLists.txt @@ -242,11 +242,9 @@ generateManual(haddMan ${CMAKE_SOURCE_DIR}/main/src/hadd-argparse.py ${CMAKE_BIN generateManual(rootclingMan ${CMAKE_SOURCE_DIR}/core/dictgen/src/rootcling-argparse.py ${CMAKE_BINARY_DIR}/man/rootcling.1) #---addRootPyCmdMan--------------------------------------------------------------------------- -#generateManual(rootbrowseMan ${CMAKE_SOURCE_DIR}/main/python/rootbrowse.py ${CMAKE_BINARY_DIR}/man/rootbrowse.1) #generateManual(rootcpMan ${CMAKE_SOURCE_DIR}/main/python/rootcp.py ${CMAKE_BINARY_DIR}/man/rootcp.1) #generateManual(rootdrawtreeMan ${CMAKE_SOURCE_DIR}/main/python/rootdrawtree.py ${CMAKE_BINARY_DIR}/man/rootdrawtree.1) #generateManual(rooteventselectorMan ${CMAKE_SOURCE_DIR}/main/python/rooteventselector.py ${CMAKE_BINARY_DIR}/man/rooteventselector.1) -#generateManual(rootlsMan ${CMAKE_SOURCE_DIR}/main/python/rootls.py ${CMAKE_BINARY_DIR}/man/rootls.1) #generateManual(rootmkdirMan ${CMAKE_SOURCE_DIR}/main/python/rootmkdir.py ${CMAKE_BINARY_DIR}/man/rootmkdir.1) #generateManual(rootmvMan ${CMAKE_SOURCE_DIR}/main/python/rootmv.py ${CMAKE_BINARY_DIR}/man/rootmv.1) #generateManual(rootprintMan ${CMAKE_SOURCE_DIR}/main/python/rootprint.py ${CMAKE_BINARY_DIR}/man/rootprint.1) diff --git a/main/python/cmdLineUtils.py b/main/python/cmdLineUtils.py index 4ab68063681bc..c05c44dcf7f4b 100644 --- a/main/python/cmdLineUtils.py +++ b/main/python/cmdLineUtils.py @@ -789,40 +789,6 @@ def deleteRootObject(rootFile, pathSplit, interactive, recursive): # End of help strings ########## -########## -# ROOTBROWSE - - -def _openBrowser(rootFile=None): - browser = ROOT.TBrowser() - if ROOT.gSystem.InheritsFrom("TMacOSXSystem") or browser.IsWeb(): - print("Press ctrl+c to exit.") - try: - while True: - if ROOT.gROOT.IsInterrupted() or ROOT.gSystem.ProcessEvents(): - break - sleep(0.01) - except (KeyboardInterrupt, SystemExit): - pass - else: - input("Press enter to exit.") - - -def rootBrowse(fileName=None): - if fileName: - rootFile = openROOTFile(fileName) - if not rootFile: - return 1 - _openBrowser(rootFile) - rootFile.Close() - else: - _openBrowser() - return 0 - - -# End of ROOTBROWSE -########## - ########## # ROOTCP diff --git a/main/python/rootbrowse.py b/main/python/rootbrowse.py deleted file mode 100755 index 13d6d5709cc2d..0000000000000 --- a/main/python/rootbrowse.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env @python@ - -# ROOT command line tools: rootbrowse -# Author: Julien Ripoche -# Mail: julien.ripoche@u-psud.fr -# Date: 20/08/15 - -"""Command line to open a ROOT file on a TBrowser""" - -import cmdLineUtils -import sys - -import ROOT - -# Help strings -description = "Open a ROOT file in a TBrowser" - -WEBON_HELP = "Configure webdisplay like chrome or qt6web" - -WEBOFF_HELP = "Invoke the normal TBrowser (not the web version)" - -EPILOG = """Examples: -- rootbrowse - Open a TBrowser - -- rootbrowse file.root - Open the ROOT file 'file.root' in a TBrowser -""" - -def get_argparse(): - # Collect arguments with the module argparse - parser = cmdLineUtils.getParserSingleFile(description, EPILOG) - parser.prog = 'rootbrowse' - - parser.add_argument("-w", "--web", help=WEBON_HELP) - parser.add_argument("-wf", "--webOff", help=WEBOFF_HELP, action="store_true") - return parser - - -def execute(): - parser = get_argparse() - - # Put arguments in shape - args = cmdLineUtils.getArgs(parser) - if args.webOff: - ROOT.gROOT.SetWebDisplay("off") - elif args.web: - ROOT.gROOT.SetWebDisplay(args.web) - - - # Process rootBrowse - return cmdLineUtils.rootBrowse(args.FILE) - -if __name__ == "__main__": - sys.exit(execute()) From 1a0fefe1b92b4e958a5e9a88e9dc1fc3f3d0e316 Mon Sep 17 00:00:00 2001 From: silverweed Date: Fri, 22 Aug 2025 15:36:42 +0200 Subject: [PATCH 3/5] [main] rootbrowse: order includes, extract short help --- main/src/rootbrowse.cxx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/main/src/rootbrowse.cxx b/main/src/rootbrowse.cxx index b5608a2ae938b..57b2a44aa9876 100644 --- a/main/src/rootbrowse.cxx +++ b/main/src/rootbrowse.cxx @@ -4,21 +4,22 @@ /// /// \author Giacomo Parolini /// \date 2025-08-21 -#include -#include -#include -#include -#include - #include +#include +#include #include #include #include #include -#include -#include +#include +#include +#include +#include +#include + +static const char *const kShortHelp = "usage: rootbrowse [-w WEB|-wf] \n"; static const char *const kLongHelp = R"( Open a ROOT file in a TBrowser @@ -29,7 +30,7 @@ positional arguments: -h, --help show this help message and exit -w, --web WEB Configure webdisplay. For all possible values, see: https://root.cern/doc/v636/classTROOT.html#a1749472696545b76a6b8e79769e7e773 - -wf, --webOff Invoke the normal TBrowser (not the web version) + -wf, --webOff Invoke the classic TBrowser (not the web version) Examples: - rootbrowse @@ -96,7 +97,7 @@ int main(int argc, char **argv) { auto args = ParseArgs(const_cast(argv) + 1, argc - 1); if (args.fPrintHelp != RootBrowseArgs::EPrintUsage::kNo) { - std::cerr << "usage: rootbrowse [-w WEB|-wf] \n"; + std::cerr << kShortHelp; if (args.fPrintHelp == RootBrowseArgs::EPrintUsage::kLong) { std::cerr << kLongHelp; return 0; From 99d004f16c6e8bc693d3342cd947652e84c8ebe9 Mon Sep 17 00:00:00 2001 From: silverweed Date: Tue, 26 Aug 2025 11:09:37 +0200 Subject: [PATCH 4/5] [rootbrowse] better option parsing, exit if in batch mode --- main/CMakeLists.txt | 4 +-- main/src/rootbrowse.cxx | 79 ++++++++++++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index b8d0610bcb6d3..9ff4ed41f76c9 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -111,9 +111,7 @@ if(WIN32) set_property(TARGET rootcling APPEND_STRING PROPERTY LINK_FLAGS " -STACK:4000000") endif() -if(gui) - ROOT_EXECUTABLE(rootbrowse src/rootbrowse.cxx LIBRARIES RIO Core Rint Gui) -endif() +ROOT_EXECUTABLE(rootbrowse src/rootbrowse.cxx LIBRARIES RIO Core Rint Gui) ROOT_EXECUTABLE(rootls src/rootls.cxx LIBRARIES RIO Tree Core Rint ROOTNTuple) # Create aliases: rootcint, genreflex. diff --git a/main/src/rootbrowse.cxx b/main/src/rootbrowse.cxx index 57b2a44aa9876..1bac6fc405f1b 100644 --- a/main/src/rootbrowse.cxx +++ b/main/src/rootbrowse.cxx @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include static const char *const kShortHelp = "usage: rootbrowse [-w WEB|-wf] \n"; static const char *const kLongHelp = R"( @@ -53,8 +55,8 @@ struct RootBrowseArgs { kLong }; EPrintUsage fPrintHelp = EPrintUsage::kNo; - const char *fWeb = "on"; - const char *fFileName = nullptr; + std::string_view fWeb; + std::string_view fFileName; }; static RootBrowseArgs ParseArgs(const char **args, int nArgs) @@ -64,25 +66,50 @@ static RootBrowseArgs ParseArgs(const char **args, int nArgs) for (int i = 0; i < nArgs; ++i) { const char *arg = args[i]; + + if (strcmp(arg, "--") == 0) { + forcePositional = true; + continue; + } + bool isFlag = !forcePositional && arg[0] == '-'; if (isFlag) { - if (strcmp(arg, "-w") == 0 || strcmp(arg, "--web") == 0) { + ++arg; + // Parse long or short flag and its argument into `argStr` / `nxtArgStr`. + std::string_view argStr, nxtArgStr; + if (arg[0] == '-') { + ++arg; + // long flag: may be either of the form `--web off` or `--web=off` + const char *eq = strchr(arg, '='); + if (eq) { + argStr = std::string_view(arg, eq - arg); + nxtArgStr = std::string_view(eq + 1); + } else { + argStr = std::string_view(arg); + if (i < nArgs - 1 && args[i + 1][0] != '-') { + nxtArgStr = args[i + 1]; + ++i; + } + } + } else { + // short flag (note that it might be more than 1 character long, like `-wf`) + argStr = std::string_view(arg); if (i < nArgs - 1 && args[i + 1][0] != '-') { + nxtArgStr = args[i + 1]; ++i; - outArgs.fWeb = args[i]; } - } else if (strcmp(arg, "-wf") == 0 || strcmp(arg, "--webOff") == 0) { - outArgs.fWeb = "off"; - } else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) { + } + + if (argStr == "w" || argStr == "web") { + outArgs.fWeb = nxtArgStr.empty() ? "on" : nxtArgStr; + } else if (argStr == "h" || argStr == "help") { outArgs.fPrintHelp = RootBrowseArgs::EPrintUsage::kLong; break; - } else if (strcmp(arg, "--") == 0) { - forcePositional = true; - } else { - outArgs.fPrintHelp = RootBrowseArgs::EPrintUsage::kShort; - break; + } else if (argStr == "wf" || argStr == "--webOff") { + outArgs.fWeb = "off"; } - } else if (outArgs.fFileName) { + + } else if (!outArgs.fFileName.empty()) { outArgs.fPrintHelp = RootBrowseArgs::EPrintUsage::kShort; break; } else { @@ -105,23 +132,35 @@ int main(int argc, char **argv) return 1; } - gROOT->SetWebDisplay(args.fWeb); + // NOTE: we need to instantiate TApplication ourselves, otherwise TBrowser + // will create a batch application that cannot show graphics. + TApplication app("rootbrowse", nullptr, nullptr); + + if (!args.fWeb.empty()) + gROOT->SetWebDisplay(std::string(args.fWeb).c_str()); std::unique_ptr file; - if (args.fFileName) { + if (!args.fFileName.empty()) { gErrorIgnoreLevel = kError; - file = std::unique_ptr(TFile::Open(args.fFileName, "READ")); + file = std::unique_ptr(TFile::Open(std::string(args.fFileName).c_str(), "READ")); if (!file || file->IsZombie()) { R__LOG_WARNING(RootBrowseLog()) << "File " << args.fFileName << " does not exist or is unreadable."; } gErrorIgnoreLevel = kUnset; } - // NOTE: we need to instantiate TApplication ourselves, otherwise TBrowser - // will create a batch application that cannot show graphics. - TApplication app("rootbrowse", nullptr, nullptr); - auto browser = std::make_unique(); + + if (gROOT->IsBatch()) + return 1; + + // For classic graphics: ensure rootbrowse quits when the window is closed + if (auto imp = browser->GetBrowserImp()) { + if (auto mainframe = imp->GetMainFrame()) { + mainframe->Connect("CloseWindow()", "TApplication", &app, "Terminate()"); + } + } + std::cout << "Press ctrl+c to exit.\n"; while (!gROOT->IsInterrupted() && !gSystem->ProcessEvents()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); From b717f19121e4a7552788f387d29e2d8c740ad4a2 Mon Sep 17 00:00:00 2001 From: silverweed Date: Thu, 28 Aug 2025 11:04:10 +0200 Subject: [PATCH 5/5] [rootbrowse] Change doc URL to latest-stable --- main/src/rootbrowse.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/rootbrowse.cxx b/main/src/rootbrowse.cxx index 1bac6fc405f1b..2b2a164104f39 100644 --- a/main/src/rootbrowse.cxx +++ b/main/src/rootbrowse.cxx @@ -30,8 +30,8 @@ positional arguments: options: -h, --help show this help message and exit - -w, --web WEB Configure webdisplay. For all possible values, see: - https://root.cern/doc/v636/classTROOT.html#a1749472696545b76a6b8e79769e7e773 + -w, --web WEB Configure webdisplay. For all possible values, see TROOT::SetWebDisplay(): + https://root.cern/doc/latest-stable/classTROOT.html#a1749472696545b76a6b8e79769e7e773 -wf, --webOff Invoke the classic TBrowser (not the web version) Examples: