Add a Frontend interface; make the process entry frontend-agnostic.
main.cpp was entirely TUI-specific (constructed Tui, parsed argv, drove
BootDispatch/DumpOutput/Run directly). Introduce a shared frontends layer so a
second frontend can reuse the whole launch flow:
- src/frontends/frontend.hpp — abstract Frontend interface (BootDispatch,
DumpCommandsMd, DumpOutput, Run), header-only, no GUI toolkit, no core dep.
- src/frontends/frontend_main.{hpp,cpp} — frontend_main(argc, argv, Frontend&):
all the argv parsing (--source/--restore/--batch/--commands-md/--help) and
the boot → batch/run flow, driving any frontend through the interface.
- Tui now implements Frontend (the four methods already matched; just marked
override).
- The TUI main.cpp shrinks to: construct Tui, call frontend_main. A second
frontend's main() is identical with its own Frontend type.
Build: a small GUI-toolkit-free static lib essim_frontend (frontend_main.cpp)
is added at the top level when a frontend is selected, and the essim exe links
it. ESSIM_FRONTEND=none still builds core+tests only (no essim_frontend, no
FTXUI). Binary stays ./build/essim.
Behaviour unchanged across --batch/--commands-md/--help/exit codes; only the
usage text is genericised ("the TUI" → "the interface", "console screen" →
"console") now that the launcher is shared.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,12 @@ if(ESSIM_FRONTEND STREQUAL "none")
|
||||
message(STATUS "essim: ESSIM_FRONTEND=none — core + tests only (no frontend, no GUI toolkit)")
|
||||
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/frontends/${ESSIM_FRONTEND}/CMakeLists.txt")
|
||||
message(STATUS "essim: building frontend '${ESSIM_FRONTEND}'")
|
||||
# Shared, GUI-toolkit-free frontend support: the abstract Frontend interface
|
||||
# (header-only) and the frontend-agnostic launcher frontend_main(). Every
|
||||
# frontend's main() links this and forwards argv to it.
|
||||
add_library(essim_frontend STATIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/frontends/frontend_main.cpp")
|
||||
target_include_directories(essim_frontend PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
add_subdirectory(src/frontends/${ESSIM_FRONTEND})
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
|
||||
30
src/frontends/frontend.hpp
Normal file
30
src/frontends/frontend.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef _FRONTEND_HPP_
|
||||
#define _FRONTEND_HPP_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
// Abstract entry-point interface every frontend (TUI, GUI, …) implements, so
|
||||
// one shared launcher (frontend_main) can drive any of them: parse argv, run
|
||||
// boot commands, optionally dump output (batch / docs) and enter the event
|
||||
// loop. Lives in the frontends layer — essim_core never depends on it.
|
||||
class Frontend {
|
||||
public:
|
||||
virtual ~Frontend() = default;
|
||||
|
||||
// Dispatch one command synchronously, exactly as if the user typed it
|
||||
// (e.g. "restore foo.essim" or "source bring-up.essim"), before the event
|
||||
// loop starts — used to seed the system at boot.
|
||||
virtual void BootDispatch(const std::string &raw) = 0;
|
||||
|
||||
// Write the command registry as Markdown (used for doc generation).
|
||||
virtual void DumpCommandsMd(std::ostream &out) const = 0;
|
||||
|
||||
// Write the accumulated console output (batch mode: no event loop).
|
||||
virtual void DumpOutput(std::ostream &out) const = 0;
|
||||
|
||||
// Enter the interactive event loop.
|
||||
virtual void Run() = 0;
|
||||
};
|
||||
|
||||
#endif // _FRONTEND_HPP_
|
||||
99
src/frontends/frontend_main.cpp
Normal file
99
src/frontends/frontend_main.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "frontends/frontend_main.hpp"
|
||||
|
||||
#include "frontends/frontend.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
void print_usage(const char *prog) {
|
||||
std::cerr <<
|
||||
"usage: " << prog << " [--batch] [--source FILE] [--restore FILE]\n"
|
||||
" " << prog << " --commands-md [FILE]\n"
|
||||
" " << prog << " --help\n"
|
||||
" (no args) launch the interface on an empty system.\n"
|
||||
" --source FILE after boot, run FILE as an essim script\n"
|
||||
" (one command per line; same as the `source`\n"
|
||||
" command). Output goes to the console.\n"
|
||||
" --restore FILE after boot, restore the system snapshot in\n"
|
||||
" FILE (same as the `restore` command).\n"
|
||||
" Combine with --source to layer a script on\n"
|
||||
" top of a restored snapshot.\n"
|
||||
" --batch run --restore/--source, print the console\n"
|
||||
" output to stdout, and exit without launching\n"
|
||||
" the interface.\n"
|
||||
" --commands-md [FILE] dump the command registry as Markdown.\n"
|
||||
" With FILE: write there. Without: stdout.\n"
|
||||
" (Used by `cmake --build build --target doc`.)\n"
|
||||
" --help, -h show this help.\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int frontend_main(int argc, char **argv, Frontend &fe) {
|
||||
std::string boot_restore;
|
||||
std::string boot_source;
|
||||
bool batch = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string a = argv[i];
|
||||
if (a == "--commands-md") {
|
||||
if (i + 1 < argc) {
|
||||
std::ofstream f(argv[++i]);
|
||||
if (!f) {
|
||||
std::cerr << "essim: cannot open " << argv[i] << " for writing\n";
|
||||
return 1;
|
||||
}
|
||||
fe.DumpCommandsMd(f);
|
||||
} else {
|
||||
fe.DumpCommandsMd(std::cout);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (a == "--source") {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "essim: --source needs a filename\n";
|
||||
return 2;
|
||||
}
|
||||
boot_source = argv[++i];
|
||||
continue;
|
||||
}
|
||||
if (a == "--restore") {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "essim: --restore needs a filename\n";
|
||||
return 2;
|
||||
}
|
||||
boot_restore = argv[++i];
|
||||
continue;
|
||||
}
|
||||
if (a == "--batch") {
|
||||
batch = true;
|
||||
continue;
|
||||
}
|
||||
if (a == "--help" || a == "-h") {
|
||||
print_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
std::cerr << "essim: unknown option: " << a << "\n";
|
||||
print_usage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Order matters: a `--restore` brings up a snapshot, then `--source`
|
||||
// can layer additional commands on top of it (useful e.g. for "load
|
||||
// snapshot, then re-run a small script that adds a new card").
|
||||
if (!boot_restore.empty()) fe.BootDispatch("restore " + boot_restore);
|
||||
if (!boot_source.empty()) fe.BootDispatch("source " + boot_source);
|
||||
|
||||
// Batch mode: the boot dispatch already ran synchronously (no event loop
|
||||
// yet), so the console output is complete. Print it and exit.
|
||||
if (batch) {
|
||||
fe.DumpOutput(std::cout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fe.Run();
|
||||
return 0;
|
||||
}
|
||||
13
src/frontends/frontend_main.hpp
Normal file
13
src/frontends/frontend_main.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef _FRONTEND_MAIN_HPP_
|
||||
#define _FRONTEND_MAIN_HPP_
|
||||
|
||||
class Frontend;
|
||||
|
||||
// Shared process entry point, frontend-agnostic. Parses argv
|
||||
// (--source / --restore / --batch / --commands-md / --help), drives `fe`
|
||||
// through the boot commands and then either dumps output (batch) or enters its
|
||||
// event loop, and returns the process exit code. Each frontend's main() just
|
||||
// constructs its concrete Frontend and forwards to this.
|
||||
int frontend_main(int argc, char **argv, Frontend &fe);
|
||||
|
||||
#endif // _FRONTEND_MAIN_HPP_
|
||||
@@ -32,7 +32,7 @@ target_link_libraries(essim_tui
|
||||
)
|
||||
|
||||
add_executable(essim "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp")
|
||||
target_link_libraries(essim PRIVATE essim_tui)
|
||||
target_link_libraries(essim PRIVATE essim_tui essim_frontend)
|
||||
|
||||
# Keep the binary at the top of the build tree (./build/essim), regardless of
|
||||
# which frontend subdir produced it.
|
||||
|
||||
@@ -1,98 +1,11 @@
|
||||
#include "frontends/frontend_main.hpp"
|
||||
#include "frontends/tui/tui.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
void print_usage(const char *prog) {
|
||||
std::cerr <<
|
||||
"usage: " << prog << " [--batch] [--source FILE] [--restore FILE]\n"
|
||||
" " << prog << " --commands-md [FILE]\n"
|
||||
" " << prog << " --help\n"
|
||||
" (no args) launch the TUI on an empty system.\n"
|
||||
" --source FILE after boot, run FILE as an essim script\n"
|
||||
" (one command per line; same as the `source`\n"
|
||||
" command). Output is in the console screen.\n"
|
||||
" --restore FILE after boot, restore the system snapshot in\n"
|
||||
" FILE (same as the `restore` command).\n"
|
||||
" Combine with --source to layer a script on\n"
|
||||
" top of a restored snapshot.\n"
|
||||
" --batch run --restore/--source, print the console\n"
|
||||
" output to stdout, and exit without the TUI.\n"
|
||||
" --commands-md [FILE] dump the command registry as Markdown.\n"
|
||||
" With FILE: write there. Without: stdout.\n"
|
||||
" (Used by `cmake --build build --target doc`.)\n"
|
||||
" --help, -h show this help.\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The TUI frontend's entry point: construct the concrete Frontend (Tui) and
|
||||
// hand off to the shared, frontend-agnostic launcher. All argv parsing and the
|
||||
// boot/batch/run flow live in frontend_main(); a second frontend's main() looks
|
||||
// exactly like this with its own Frontend type.
|
||||
int main(int argc, char **argv) {
|
||||
std::string boot_restore;
|
||||
std::string boot_source;
|
||||
bool batch = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string a = argv[i];
|
||||
if (a == "--commands-md") {
|
||||
Tui tui;
|
||||
if (i + 1 < argc) {
|
||||
std::ofstream f(argv[++i]);
|
||||
if (!f) {
|
||||
std::cerr << "essim: cannot open " << argv[i] << " for writing\n";
|
||||
return 1;
|
||||
}
|
||||
tui.DumpCommandsMd(f);
|
||||
} else {
|
||||
tui.DumpCommandsMd(std::cout);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (a == "--source") {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "essim: --source needs a filename\n";
|
||||
return 2;
|
||||
}
|
||||
boot_source = argv[++i];
|
||||
continue;
|
||||
}
|
||||
if (a == "--restore") {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "essim: --restore needs a filename\n";
|
||||
return 2;
|
||||
}
|
||||
boot_restore = argv[++i];
|
||||
continue;
|
||||
}
|
||||
if (a == "--batch") {
|
||||
batch = true;
|
||||
continue;
|
||||
}
|
||||
if (a == "--help" || a == "-h") {
|
||||
print_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
std::cerr << "essim: unknown option: " << a << "\n";
|
||||
print_usage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
Tui tui;
|
||||
// Order matters: a `--restore` brings up a snapshot, then `--source`
|
||||
// can layer additional commands on top of it (useful e.g. for "load
|
||||
// snapshot, then re-run a small script that adds a new card").
|
||||
if (!boot_restore.empty()) tui.BootDispatch("restore " + boot_restore);
|
||||
if (!boot_source.empty()) tui.BootDispatch("source " + boot_source);
|
||||
|
||||
// Batch mode: the boot dispatch already ran synchronously (no screen yet),
|
||||
// so the console output is complete. Print it and exit without the TUI.
|
||||
if (batch) {
|
||||
tui.DumpOutput(std::cout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tui.Run();
|
||||
return 0;
|
||||
return frontend_main(argc, argv, tui);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/screen_interactive.hpp>
|
||||
|
||||
#include "frontends/frontend.hpp"
|
||||
|
||||
class System;
|
||||
|
||||
class Tui {
|
||||
class Tui : public Frontend {
|
||||
enum class Completion { None, Path, Command };
|
||||
|
||||
struct Prompt {
|
||||
@@ -198,16 +200,16 @@ private:
|
||||
public:
|
||||
Tui();
|
||||
~Tui();
|
||||
void Run();
|
||||
void DumpCommandsMd(std::ostream &out) const;
|
||||
void Run() override;
|
||||
void DumpCommandsMd(std::ostream &out) const override;
|
||||
// Write the accumulated console output to `out`. Used by batch mode to
|
||||
// surface a script's output without starting the TUI.
|
||||
void DumpOutput(std::ostream &out) const;
|
||||
void DumpOutput(std::ostream &out) const override;
|
||||
|
||||
// Boot-time hook: dispatch a single command exactly as if the user
|
||||
// typed it (e.g. `restore foo.essim` or `source bring-up.essim`).
|
||||
// Call before `Run()` to seed the system before the event loop starts.
|
||||
void BootDispatch(const std::string &raw);
|
||||
void BootDispatch(const std::string &raw) override;
|
||||
|
||||
private:
|
||||
// Lifecycle (commands.cpp)
|
||||
|
||||
Reference in New Issue
Block a user