From bfacc111a4b55a7dfd5d71d56fde5d562290675b Mon Sep 17 00:00:00 2001 From: Hleb Valoshka <375gnu@gmail.com> Date: Tue, 29 Jun 2021 22:46:35 +0400 Subject: [PATCH 1/5] wip --- src/celcompat/fs.cpp | 62 +++++++++++++++ src/celcompat/fs.h | 5 ++ src/celestia/celestiacore.cpp | 84 +++++++++++++++++--- src/celestia/celestiacore.h | 24 ++++-- src/celestia/gtk/main.cpp | 75 +++--------------- src/celestia/qt/qtappwin.cpp | 45 +---------- src/celestia/qt/qtappwin.h | 4 +- src/celestia/qt/qtmain.cpp | 120 +--------------------------- src/celestia/win32/winmain.cpp | 127 +++++------------------------- src/celutil/CMakeLists.txt | 2 + src/celutil/array_view.h | 11 ++- src/celutil/cmdline.cpp | 140 +++++++++++++++++++++++++++++++++ src/celutil/cmdline.h | 67 ++++++++++++++++ src/tools/CMakeLists.txt | 1 - 14 files changed, 408 insertions(+), 359 deletions(-) create mode 100644 src/celutil/cmdline.cpp create mode 100644 src/celutil/cmdline.h diff --git a/src/celcompat/fs.cpp b/src/celcompat/fs.cpp index 7327b13e3de..10d37858381 100644 --- a/src/celcompat/fs.cpp +++ b/src/celcompat/fs.cpp @@ -5,6 +5,7 @@ #include #else #include +#include #endif #if defined(_MSC_VER) && !defined(__clang__) // M$VC++ build without C++ exceptions are not supported yet @@ -594,5 +595,66 @@ bool create_directory(const path& p) return r; } +path current_path() +{ + std::error_code ec; + path p = current_path(ec); + + if (ec) +#if __cpp_exceptions + throw filesystem_error(ec, "celfs::current_path error"); +#else + std::abort(); +#endif + return p; +} +path current_path(std::error_code& ec) +{ +#ifdef _WIN32 + std::wstring p(MAX_PATH + 1, 0); + DWORD r = GetModuleFileNameW(nullptr, &p[0], MAX_PATH); + if (r == 0) + { + ec = std::error_code(errno, std::system_category()); + return path(); + } + auto pos = buffer.find_last_of(L"\\/"); + return buffer.substr(0, pos); +#else + std::string p(256, 0); + char *r = getcwd(&p[0], p.size()); + if (r == nullptr) + { + ec = std::error_code(errno, std::system_category()); + return path(); + } + return p; +#endif +} + +void current_path(const path& p) +{ + std::error_code ec; + current_path(p, ec); + if (ec) +#if __cpp_exceptions + throw filesystem_error(ec, "celfs::current_path error"); +#else + std::abort(); +#endif +} + +void current_path(const path& p, std::error_code& ec) noexcept +{ +#ifdef _WIN32 + BOOL ret = SetCurrentDirectoryW(p.c_str()); + if (!ret) + ec = std::error_code(errno, std::system_category()); +#else + int ret = chdir(p.c_str()); + if (ret != 0) + ec = std::error_code(errno, std::system_category()); +#endif +} } } diff --git a/src/celcompat/fs.h b/src/celcompat/fs.h index 71feb3c37dc..d25ef916e75 100644 --- a/src/celcompat/fs.h +++ b/src/celcompat/fs.h @@ -470,5 +470,10 @@ bool is_directory(const path& p, std::error_code& ec) noexcept; bool create_directory(const path& p); bool create_directory(const path& p, std::error_code& ec) noexcept; + +path current_path(); +path current_path(std::error_code& ec); +void current_path(const path& p); +void current_path(const path& p, std::error_code& ec) noexcept; } } diff --git a/src/celestia/celestiacore.cpp b/src/celestia/celestiacore.cpp index c6c2deb1eca..909a57f29d4 100644 --- a/src/celestia/celestiacore.cpp +++ b/src/celestia/celestiacore.cpp @@ -3680,13 +3680,15 @@ using StarLoader = CatalogLoader; using DeepSkyLoader = CatalogLoader; -bool CelestiaCore::initSimulation(const fs::path& configFileName, - const vector& extrasDirs, - ProgressNotifier* progressNotifier) +bool CelestiaCore::initSimulation(ProgressNotifier* progressNotifier) { - if (!configFileName.empty()) + initDataPath(); + std::error_code ec; + fs::current_path(m_dataPath, ec); + + if (!m_configFileName.empty()) { - config = ReadCelestiaConfig(configFileName); + config = ReadCelestiaConfig(m_configFileName); } else { @@ -3723,12 +3725,12 @@ bool CelestiaCore::initSimulation(const fs::path& configFileName, // after the ones from the config file and the order in which they were // specified is preserved. This process in O(N*M), but the number of // additional extras directories should be small. - for (const auto& dir : extrasDirs) + for (const auto& dir : m_extrasDirs) { - if (find(config->extrasDirs.begin(), config->extrasDirs.end(), dir.string()) == + if (find(config->extrasDirs.begin(), config->extrasDirs.end(), dir) == config->extrasDirs.end()) { - config->extrasDirs.push_back(dir.string()); + config->extrasDirs.push_back(dir); } } @@ -4764,7 +4766,7 @@ bool CelestiaCore::saveScreenShot(const fs::path& filename, ContentType type) co return false; } -void CelestiaCore::setLogFile(fs::path &fn) +void CelestiaCore::setLogFile(const fs::path &fn) { m_logfile = std::ofstream(fn.string()); if (m_logfile.good()) @@ -4779,6 +4781,39 @@ void CelestiaCore::setLogFile(fs::path &fn) } } +bool CelestiaCore::initDataPath() +{ + // was set using command line + if (!m_dataPath.empty()) + return true; + +#ifdef _WIN32 + const wchar_t *d = _wgetenv(L"CELESTIA_DATA_DIR"); +#else + const char *d = getenv("CELESTIA_DATA_DIR"); +#endif + if (d != nullptr) + { + m_dataPath = fs::path(d); + return true; + } + +#ifdef NATIVE_OSX_APP + // On macOS data directory is in a fixed position relative to the + // application bundle + error_code ec; + auto curDir = fs::current_path(ec); + if (ec != error_code()) + return false; + assert(curDir.is_absolute()); + m_dataPath = curDir.parent_path() / "Resources"; +#else + m_dataPath = fs::path(CONFIG_DATA_DIR); +#endif + + return true; +} + #ifdef USE_FFMPEG auto CelestiaCore::getSupportedMovieSizes() const -> celestia::util::array_view @@ -4800,9 +4835,9 @@ auto CelestiaCore::getSupportedMovieSizes() const auto CelestiaCore::getSupportedMovieFramerates() const -> celestia::util::array_view { - static std::array MovieFramerates = + static std::array MovieFramerates = { - 15.0f, 24.0f, 25.0f, 29.97f, 30.0f + 15.0f, 24.0f, 25.0f, 29.97f, 30.0f, 60.0f }; return MovieFramerates; } @@ -4818,3 +4853,30 @@ auto CelestiaCore::getSupportedMovieCodecs() const return MovieCodecs; } #endif + +static void CommandLineError(const char *message) +{ + cout << message << '\n'; +} + +util::CmdLineParser CelestiaCore::getCommandLineParser() +{ + util::CmdLineParser parser; + parser.on("conf", 'c', true, + _("Configuration file name expected after --conf/-c"), + [this](const char *v) { m_configFileName = v; return true; }); + parser.on("dir", 'd', true, + _("Directory expected after --dir/-d"), + [this](const char *v) { m_dataPath = v; return true; }); + parser.on("extrasdir", 'e', true, + _("Directory expected after --extrasdir/-e"), + [this](const char *v) { m_extrasDirs.push_back(v); return true; }); + parser.on("url", 'u', true, + _("URL expected after --url/-u"), + [this](const char *v) { setStartURL(v); return true; }); + parser.on("log", 'l', true, + _("A filename expected after --log/-l"), + [this](const char *v) { setLogFile(v); return true; }); + + return parser; +} diff --git a/src/celestia/celestiacore.h b/src/celestia/celestiacore.h index 79c7f7d1bae..fecbb7fb9f1 100644 --- a/src/celestia/celestiacore.h +++ b/src/celestia/celestiacore.h @@ -13,7 +13,10 @@ #include #include #include +#include +#include #include +#include #include #include // #include @@ -25,7 +28,6 @@ #include #include #include -#include #include "configfile.h" #include "favorites.h" #include "destination.h" @@ -195,9 +197,7 @@ class CelestiaCore // : public Watchable CelestiaCore(); ~CelestiaCore(); - bool initSimulation(const fs::path& configFileName = fs::path(), - const std::vector& extrasDirs = {}, - ProgressNotifier* progressNotifier = nullptr); + bool initSimulation(ProgressNotifier* progressNotifier = nullptr); bool initRenderer(); void start(double t); void start(); @@ -322,12 +322,12 @@ class CelestiaCore // : public Watchable void notifyWatchers(int); - void setLogFile(fs::path&); + void setLogFile(const fs::path&); class Alerter { public: - virtual ~Alerter() {}; + virtual ~Alerter() = default; virtual void fatalError(const std::string&) = 0; }; @@ -337,7 +337,7 @@ class CelestiaCore // : public Watchable class CursorHandler { public: - virtual ~CursorHandler() {}; + virtual ~CursorHandler() = default; virtual void setCursorShape(CursorShape) = 0; virtual CursorShape getCursorShape() const = 0; }; @@ -389,6 +389,8 @@ class CelestiaCore // : public Watchable Image captureImage() const; bool saveScreenShot(const fs::path&, ContentType = Content_Unknown) const; + celestia::util::CmdLineParser getCommandLineParser(); + protected: bool readStars(const CelestiaConfig&, ProgressNotifier*); void renderOverlay(); @@ -397,6 +399,8 @@ class CelestiaCore // : public Watchable #endif // CELX private: + bool initDataPath(); + CelestiaConfig* config{ nullptr }; Universe* universe{ nullptr }; @@ -538,6 +542,12 @@ class CelestiaCore // : public Watchable std::ofstream m_logfile; teestream m_tee; + // options passed through command line + fs::path m_dataPath; + fs::path m_configFileName; + fs::path m_logFilename; + std::vector m_extrasDirs; + #ifdef CELX friend View* getViewByObserver(CelestiaCore*, Observer*); friend void getObservers(CelestiaCore*, std::vector&); diff --git a/src/celestia/gtk/main.cpp b/src/celestia/gtk/main.cpp index 44af4c4898b..b287b8b7d56 100644 --- a/src/celestia/gtk/main.cpp +++ b/src/celestia/gtk/main.cpp @@ -75,24 +75,9 @@ static void initRealize(GtkWidget* widget, AppData* app); /* Command-Line Options */ -static gchar* configFile = NULL; -static gchar* installDir = NULL; -static gchar** extrasDir = NULL; static gboolean fullScreen = FALSE; static gboolean noSplash = FALSE; -/* Command-Line Options specification */ -static GOptionEntry optionEntries[] = -{ - { "conf", 'c', 0, G_OPTION_ARG_FILENAME, &configFile, "Alternate configuration file", "file" }, - { "dir", 'd', 0, G_OPTION_ARG_FILENAME, &installDir, "Alternate installation directory", "directory" }, - { "extrasdir", 'e', 0, G_OPTION_ARG_FILENAME_ARRAY, &extrasDir, "Additional \"extras\" directory", "directory" }, - { "fullscreen", 'f', 0, G_OPTION_ARG_NONE, &fullScreen, "Start full-screen", NULL }, - { "nosplash", 's', 0, G_OPTION_ARG_NONE, &noSplash, "Disable splash screen", NULL }, - { NULL, '\0', 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } -}; - - /* Initializes GtkActions and creates main menu */ static void createMainMenu(GtkWidget* window, AppData* app) { @@ -339,34 +324,6 @@ int main(int argc, char* argv[]) /* Watcher enables sending signals from inside of core */ GtkWatcher* gtkWatcher; - /* Command line option parsing */ - GError *error = NULL; - GOptionContext* context = g_option_context_new(""); - g_option_context_add_main_entries(context, optionEntries, NULL); - g_option_context_add_group(context, gtk_get_option_group(TRUE)); - g_option_context_parse(context, &argc, &argv, &error); - - if (error != NULL) - { - g_print("Error in command line options. Use --help for full list.\n"); - exit(1); - } - - /* At this point, the argument count should be 1 or 2, with the lastX - * potentially being a cel:// URL. */ - - /* If there's an argument left, assume it's a URL. This happens here - * because it's after the saved prefs are applied. The appCore gets - * initialized elsewhere. */ - if (argc > 1) - app->startURL = argv[argc - 1]; - - if (installDir == NULL) - installDir = (gchar*)CONFIG_DATA_DIR; - - if (chdir(installDir) == -1) - cerr << "Cannot chdir to '" << installDir << "', probably due to improper installation.\n"; - #ifdef GNOME /* GNOME Initialization */ GnomeProgram *program; @@ -384,34 +341,20 @@ int main(int argc, char* argv[]) SetDebugVerbosity(0); app->core = new CelestiaCore(); - if (app->core == NULL) - { - cerr << "Failed to initialize Celestia core.\n"; - return 1; - } + auto parser = app->core->getCommandLineParser(); + parser.on("fullscreen", 'f', false, + _("Start full-screen"), + [](bool) { fullScreen = TRUE; }); + parser.on("nosplash", 's', false, + _("Disable splash screen"), + [](bool) { noSplash = TRUE; }); + parser.parse(argc, argv); app->renderer = app->core->getRenderer(); g_assert(app->renderer); - /* Parse simulation arguments */ - string altConfig; - if (configFile != NULL) - altConfig = string(configFile); - - vector configDirs; - if (extrasDir != NULL) - { - /* Add each extrasDir to the vector */ - int i = 0; - while (extrasDir[i] != NULL) - { - configDirs.push_back(extrasDir[i]); - i++; - } - } - /* Initialize the simulation */ - if (!app->core->initSimulation(altConfig, configDirs, ss->notifier)) + if (!app->core->initSimulation(ss->notifier)) return 1; app->simulation = app->core->getSimulation(); diff --git a/src/celestia/qt/qtappwin.cpp b/src/celestia/qt/qtappwin.cpp index 5df62274026..b22dc94f7dd 100644 --- a/src/celestia/qt/qtappwin.cpp +++ b/src/celestia/qt/qtappwin.cpp @@ -187,43 +187,12 @@ CelestiaAppWindow::~CelestiaAppWindow() } -void CelestiaAppWindow::init(const QString& qConfigFileName, - const QStringList& qExtrasDirectories, - const QString& logFilename) +void CelestiaAppWindow::init(int argc, char *argv[]) { - QString celestia_data_dir = QString::fromLocal8Bit(::getenv("CELESTIA_DATA_DIR")); - - if (celestia_data_dir.isEmpty()) { -#ifdef NATIVE_OSX_APP - // On macOS data directory is in a fixed position relative to the application bundle - QString dataDir = QApplication::applicationDirPath() + "/../Resources"; -#else - QString dataDir = CONFIG_DATA_DIR; -#endif - QString celestia_data_dir = dataDir; - QDir::setCurrent(celestia_data_dir); - } else if (QDir(celestia_data_dir).isReadable()) { - QDir::setCurrent(celestia_data_dir); - } else { - QMessageBox::critical(0, "Celestia", - _("Celestia is unable to run because the data directory was not " - "found, probably due to improper installation.")); - exit(1); - } - - // Get the config file name - string configFileName; - if (!qConfigFileName.isEmpty()) - configFileName = qConfigFileName.toStdString(); - - // Translate extras directories from QString -> std::string - vector extrasDirectories; - for (const auto& dir : qExtrasDirectories) - extrasDirectories.push_back(dir.toUtf8().data()); - initAppDataDirectory(); m_appCore = new CelestiaCore(); + m_appCore->getCommandLineParser().parse(argc, argv); auto* progress = new AppProgressNotifier(this); alerter = new AppAlerter(this); @@ -231,15 +200,7 @@ void CelestiaAppWindow::init(const QString& qConfigFileName, setWindowIcon(QIcon(":/icons/celestia.png")); - if (!logFilename.isEmpty()) - { - fs::path fn(logFilename.toStdString()); - m_appCore->setLogFile(fn); - } - - if (!m_appCore->initSimulation(configFileName, - extrasDirectories, - progress)) + if (!m_appCore->initSimulation(progress)) { // Error message is shown by celestiacore so we silently exit here. exit(1); diff --git a/src/celestia/qt/qtappwin.h b/src/celestia/qt/qtappwin.h index f72b8658b69..38bc467e8fa 100644 --- a/src/celestia/qt/qtappwin.h +++ b/src/celestia/qt/qtappwin.h @@ -40,9 +40,7 @@ class CelestiaAppWindow : public QMainWindow, public CelestiaCore::ContextMenuHa CelestiaAppWindow(QWidget* parent = nullptr); ~CelestiaAppWindow(); - void init(const QString& configFileName, - const QStringList& extrasDirectories, - const QString& logFilename); + void init(int argc, char *argv[]); void readSettings(); void writeSettings(); diff --git a/src/celestia/qt/qtmain.cpp b/src/celestia/qt/qtmain.cpp index c8262d454a2..1e87ac6d923 100644 --- a/src/celestia/qt/qtmain.cpp +++ b/src/celestia/qt/qtmain.cpp @@ -33,22 +33,6 @@ using namespace std; -//static const char *description = "Celestia"; - -// Command line options -static bool startFullscreen = false; -static bool runOnce = false; -static QString startURL; -static QString logFilename; -static QString startDirectory; -static QString startScript; -static QStringList extrasDirectories; -static QString configFileName; -static bool useAlternateConfigFile = false; -static bool skipSplashScreen = false; - -static bool ParseCommandLine(); - int main(int argc, char *argv[]) { #ifndef GL_ES @@ -71,8 +55,6 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("Celestia Development Team"); QCoreApplication::setApplicationName("Celestia QT"); - ParseCommandLine(); - #ifdef NATIVE_OSX_APP // On macOS data directory is in a fixed position relative to the application bundle QDir splashDir(QApplication::applicationDirPath() + "/../Resources/splash"); @@ -112,7 +94,7 @@ int main(int argc, char *argv[]) QObject::connect(&window, SIGNAL(progressUpdate(const QString&, int, const QColor&)), &splash, SLOT(showMessage(const QString&, int, const QColor&))); - window.init(configFileName, extrasDirectories, logFilename); + window.init(argc, argv); window.show(); splash.finish(&window); @@ -122,103 +104,3 @@ int main(int argc, char *argv[]) return app.exec(); } - - - -static void CommandLineError(const char* /*unused*/) -{ -} - - - -bool ParseCommandLine() -{ - QStringList args = QCoreApplication::arguments(); - - int i = 1; - - while (i < args.size()) - { - bool isLastArg = (i == args.size() - 1); -#if 0 - if (strcmp(argv[i], "--verbose") == 0) - { - SetDebugVerbosity(1); - } - else -#endif - if (args.at(i) == "--fullscreen") - { - startFullscreen = true; - } - else if (args.at(i) == "--once") - { - runOnce = true; - } - else if (args.at(i) == "--dir") - { - if (isLastArg) - { - CommandLineError(_("Directory expected after --dir")); - return false; - } - i++; - startDirectory = args.at(i); - } - else if (args.at(i) == "--conf") - { - if (isLastArg) - { - CommandLineError(_("Configuration file name expected after --conf")); - return false; - } - i++; - configFileName = args.at(i); - useAlternateConfigFile = true; - } - else if (args.at(i) == "--extrasdir") - { - if (isLastArg) - { - CommandLineError(_("Directory expected after --extrasdir")); - return false; - } - i++; - extrasDirectories.push_back(args.at(i)); - } - else if (args.at(i) == "-u" || args.at(i) == "--url") - { - if (isLastArg) - { - CommandLineError(_("URL expected after --url")); - return false; - } - i++; - startURL = args.at(i); - } - else if (args.at(i) == "-s" || args.at(i) == "--nosplash") - { - skipSplashScreen = true; - } - else if (args.at(i) == "-l" || args.at(i) == "--log") - { - if (isLastArg) - { - CommandLineError(_("A filename expected after --log/-l")); - return false; - } - i++; - logFilename = args.at(i); - } - else - { - string buf = fmt::sprintf(_("Invalid command line option '%s'"), args.at(i).toUtf8().data()); - CommandLineError(buf.c_str()); - return false; - } - - i++; - } - - return true; -} diff --git a/src/celestia/win32/winmain.cpp b/src/celestia/win32/winmain.cpp index 001dc43ba09..c84ffa8b279 100644 --- a/src/celestia/win32/winmain.cpp +++ b/src/celestia/win32/winmain.cpp @@ -76,6 +76,11 @@ typedef vector IntStrPairVec; char AppName[] = "Celestia"; +// command line options +static bool startFullscreen = false; +static bool runOnce = false; +static bool skipSplashScreen = false; + static CelestiaCore* appCore = NULL; // Display modes for full screen operation @@ -2997,110 +3002,6 @@ static char** splitCommandLine(LPSTR cmdLine, } -static bool startFullscreen = false; -static bool runOnce = false; -static string startURL; -static string startDirectory; -static string startScript; -static vector extrasDirectories; -static string configFileName; -static bool useAlternateConfigFile = false; -static bool skipSplashScreen = false; - -static bool parseCommandLine(int argc, char* argv[]) -{ - int i = 0; - - while (i < argc) - { - bool isLastArg = (i == argc - 1); - if (strcmp(argv[i], "--verbose") == 0) - { - SetDebugVerbosity(1); - } - else if (strcmp(argv[i], "--fullscreen") == 0) - { - startFullscreen = true; - } - else if (strcmp(argv[i], "--once") == 0) - { - runOnce = true; - } - else if (strcmp(argv[i], "--dir") == 0) - { - if (isLastArg) - { - MessageBox(NULL, - _("Directory expected after --dir"), - _("Celestia Command Line Error"), - MB_OK | MB_ICONERROR); - return false; - } - i++; - startDirectory = string(argv[i]); - } - else if (strcmp(argv[i], "--conf") == 0) - { - if (isLastArg) - { - MessageBox(NULL, - _("Configuration file name expected after --conf"), - _("Celestia Command Line Error"), - MB_OK | MB_ICONERROR); - return false; - } - i++; - configFileName = string(argv[i]); - useAlternateConfigFile = true; - } - else if (strcmp(argv[i], "--extrasdir") == 0) - { - if (isLastArg) - { - MessageBox(NULL, - _("Directory expected after --extrasdir"), - _("Celestia Command Line Error"), - MB_OK | MB_ICONERROR); - return false; - } - i++; - extrasDirectories.push_back(string(argv[i])); - } - else if (strcmp(argv[i], "-u") == 0 || strcmp(argv[i], "--url") == 0) - { - if (isLastArg) - { - MessageBox(NULL, - _("URL expected after --url"), - _("Celestia Command Line Error"), - MB_OK | MB_ICONERROR); - return false; - } - i++; - startURL = string(argv[i]); - } - else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--nosplash") == 0) - { - skipSplashScreen = true; - } - else - { - char* buf = new char[strlen(argv[i]) + 256]; - sprintf(buf, _("Invalid command line option '%s'"), argv[i]); - MessageBox(NULL, - buf, _("Celestia Command Line Error"), - MB_OK | MB_ICONERROR); - delete[] buf; - return false; - } - - i++; - } - - return true; -} - - class WinSplashProgressNotifier : public ProgressNotifier { public: @@ -3130,9 +3031,19 @@ int APIENTRY WinMain(HINSTANCE hInstance, int argc; char** argv; argv = splitCommandLine(lpCmdLine, argc); - bool cmdLineOK = parseCommandLine(argc, argv); - if (!cmdLineOK) - return 1; + + appCore = new CelestiaCore(); + auto parser = appCore->getCommandLineParser(); + parser.on("fullscreen", 'f', false, + _("Start full-screen"), + [](bool) { startFullscreen = true; }); + parser.on("nosplash", 's', false, + _("Disable splash screen"), + [](bool) { skipSplashScreen = true; }); + parser.on("once", 'o', false, + _("Run only one Celestia instance"), + [](bool) { runOnce = true; }); + parser.parse(argc, argv); // If Celestia was invoked with the --once command line parameter, // check to see if there's already an instance of Celestia running. @@ -3252,8 +3163,6 @@ int APIENTRY WinMain(HINSTANCE hInstance, lastFullScreenMode = fallbackFullScreenMode; } - appCore = new CelestiaCore(); - // Gettext integration setlocale(LC_ALL, ""); setlocale(LC_NUMERIC, "C"); diff --git a/src/celutil/CMakeLists.txt b/src/celutil/CMakeLists.txt index 7e86dc0cc97..e4afdb9ce48 100644 --- a/src/celutil/CMakeLists.txt +++ b/src/celutil/CMakeLists.txt @@ -3,6 +3,8 @@ set(CELUTIL_SOURCES bigfix.h blockarray.h bytes.h + cmdline.cpp + cmdline.h color.cpp color.h debug.cpp diff --git a/src/celutil/array_view.h b/src/celutil/array_view.h index 8f900e61027..8705fcced83 100644 --- a/src/celutil/array_view.h +++ b/src/celutil/array_view.h @@ -39,13 +39,22 @@ class array_view ~array_view() noexcept = default; /** - * Wrap a C-style array. + * Wrap a compile-time C-style array. */ template constexpr array_view(const T (&ary)[N]) noexcept : m_ptr(ary), m_size(N) {}; + + /** + * Wrap a run-time C-style array. + */ + constexpr array_view(const T ary[], size_t n) noexcept : + m_ptr(ary), + m_size(n) + {}; + /** * Wrap a std::array or std::vector or other classes which have the same * memory layout and interface. diff --git a/src/celutil/cmdline.cpp b/src/celutil/cmdline.cpp new file mode 100644 index 00000000000..2b6bcc662c7 --- /dev/null +++ b/src/celutil/cmdline.cpp @@ -0,0 +1,140 @@ +#include "cmdline.h" + +namespace celestia +{ +namespace util +{ +CmdLineParser::Option::Option(const char *longOption, + char shortOption, + bool hasValue, + std::function handler) : + hasValue(hasValue), + handler1(handler) +{ + this->longOption = fmt::format("--{}", longOption); + this->shortOption = fmt::format("-{}", shortOption); +} + +CmdLineParser::Option::Option(const char *longOption, + char shortOption, + bool hasValue, + std::function handler) : + hasValue(hasValue), + handler0(handler) +{ + this->longOption = fmt::format("--{}", longOption); + this->shortOption = fmt::format("-{}", shortOption); +} + +CmdLineParser& +CmdLineParser::on(const char *longOption, + char shortOption, + bool hasValue, + const char *errorMessage, + std::function handler) +{ + (void) errorMessage; + m_options.emplace_back(longOption, shortOption, hasValue, handler); + return *this; +} + +CmdLineParser& +CmdLineParser::on(const char *longOption, + char shortOption, + bool hasValue, + const char *errorMessage, + std::function handler) +{ + (void) errorMessage; + m_options.emplace_back(longOption, shortOption, hasValue, handler); + return *this; +} + +CmdLineParser& +CmdLineParser::on(const CmdLineParser::Option &option) +{ + m_options.push_back(option); + return *this; +} + +const char* +CmdLineParser::getBadOption() const noexcept +{ + return m_badOption; +} + +CmdLineParser::ErrorClass +CmdLineParser::getError() const noexcept +{ + return m_ec; +} + +const char* +CmdLineParser::getErrorString() const noexcept +{ + switch (m_ec) + { + case NoError: + return "no error"; + case ArgumentMissing: + return "argument missing"; + case UnknownOption: + return "unknown option"; + default: + return "unknown error"; + } +} + +bool +CmdLineParser::parse(int argc, char *argv[]) +{ + for (int i = 1; i < argc; i++) + { + const char* arg = argv[i]; + auto op = [arg](const Option &option) { + return option.longOption == arg || option.shortOption == arg; + }; + + auto it = std::find_if(m_options.begin(), m_options.end(), op); + if (it == m_options.end()) + { + m_ec = UnknownOption; + m_badOption = argv[i]; + return false; + } + if (it->hasValue) + { + if (i == argc - 1) + { + m_ec = ArgumentMissing; + m_badOption = argv[i]; + return false; + } + + it->handler1(argv[++i]); + } + else + { + it->handler0(true); + } + } + return true; +} +} +} +#if 0 +using namespace celestia::util; +#include +int main(int argc, char *argv[]) +{ + CmdLineParser cmdline; + cmdline.on("value", 'v', true, "", [](const char *value) { std::cout << value << '\n'; return true; }) + .on("bool", 'b', false, "", [](bool on) { std::cout << "bool=" << on << '\n'; }); + if (!cmdline.parse(argc, argv)) + { + std::cerr << "Bad option: " << cmdline.getBadOption() << ", " << cmdline.getErrorString() << '\n'; + } + std::cout << sizeof(std::function) << ' ' << sizeof(std::function) << '\n'; + return 0; +} +#endif diff --git a/src/celutil/cmdline.h b/src/celutil/cmdline.h new file mode 100644 index 00000000000..ca756f158e9 --- /dev/null +++ b/src/celutil/cmdline.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace celestia +{ +namespace util +{ +class CmdLineParser +{ + public: + enum ErrorClass + { + NoError = 0, + ArgumentMissing = 1, + UnknownOption = 2, + }; + + struct Option + { + std::string longOption; + std::string shortOption; + bool hasValue; + std::function handler1; + std::function handler0; + + Option(const char *longOption, + char shortOption, + bool hasValue, + std::function handler); + + Option(const char *longOption, + char shortOption, + bool hasValue, + std::function handler); + }; + + bool parse(int argc, char *argv[]); + const char* getBadOption() const noexcept; + ErrorClass getError() const noexcept; + const char* getErrorString() const noexcept; + + CmdLineParser& on(const char *longOption, + char shortOption, + bool hasValue, + const char *errorMessage, + std::function handler); + + CmdLineParser& on(const char *longOption, + char shortOption, + bool hasValue, + const char *errorMessage, + std::function handler); + + CmdLineParser& on(const Option &option); + + private: + std::vector