MaCh3  2.8.0
Reference Guide
Public Member Functions | Private Member Functions | Private Attributes | Static Private Attributes | List of all members
M3::MaCh3Program Class Reference

Main program class that manages modules, plugins, and command-line parsing. More...

#include <CLI/MaCh3Program.hpp>

Inheritance diagram for M3::MaCh3Program:
[legend]
Collaboration diagram for M3::MaCh3Program:
[legend]

Public Member Functions

virtual ~MaCh3Program ()
 Destructor that unloads all dynamic plugins. More...
 
void add_core_module (IModule &module)
 Add a core module to the program. More...
 
void load_dynamic_plugins ()
 Load dynamic plugins from the MACH3_PLUGINS environment variable. More...
 
void install_completions () const
 Install shell completion scripts for bash and zsh. More...
 
void completions (const std::string &prefix, const std::vector< std::string > &context) const
 Generate completion suggestions given the current prefix and preceding words. More...
 
void unload_dynamic_plugins ()
 Unload all dynamically loaded plugins. More...
 
int Run ()
 Run the selected subcommand. More...
 
- Public Member Functions inherited from M3::MaCh3ArgumentParser
virtual ~MaCh3ArgumentParser ()=default
 
const std::string name () const
 Get the name of this parser/subcommand. More...
 
const std::list< std::reference_wrapper< ArgumentParser > > & subparsers () const
 Get the list of registered subparsers. More...
 
const MaCh3ArgumentParser & get_subcommand_used () const
 Get the subcommand that was used in parsing. More...
 
std::vector< std::string > get_long_option_names () const
 Return all long option names (–xxx) registered with this parser. More...
 
bool option_takes_value (const std::string &name) const
 Return true if the named option expects a value (not a flag) More...
 

Private Member Functions

std::string detect_shell () const
 Detect the user's shell from the SHELL environment variable. More...
 
bool write_file (const fs::path &path, std::string_view content) const
 Write content to a file, creating parent directories if needed. More...
 
std::vector< std::string > expand_plugin_path (const std::string &path) const
 Expand a plugin path (file or directory) into a list of .so files. More...
 

Private Attributes

std::vector< std::string > m_subcommands
 List of registered subcommand names. More...
 
std::map< const MaCh3ArgumentParser *, IModule * > m_module_map
 Map of parsers to core modules (non-owning) More...
 
std::map< const MaCh3ArgumentParser *, std::unique_ptr< DynamicPlugin > > m_dynamic_plugin_map
 Map of parsers to dynamic plugins (owning) More...
 

Static Private Attributes

static constexpr std::string_view BASH_COMPLETION
 
static constexpr std::string_view ZSH_COMPLETION
 

Detailed Description

Main program class that manages modules, plugins, and command-line parsing.

This class extends MaCh3ArgumentParser to provide functionality for:

Author
Alexander Richards

Definition at line 25 of file MaCh3Program.hpp.

Constructor & Destructor Documentation

◆ ~MaCh3Program()

virtual M3::MaCh3Program::~MaCh3Program ( )
inlinevirtual

Destructor that unloads all dynamic plugins.

Definition at line 30 of file MaCh3Program.hpp.

30  {
31  //std::cout<<"UNLOADED"<< std::endl;
32  this->unload_dynamic_plugins();
33  }
void unload_dynamic_plugins()
Unload all dynamically loaded plugins.

Member Function Documentation

◆ add_core_module()

void M3::MaCh3Program::add_core_module ( IModule &  module)

Add a core module to the program.

Parameters
moduleReference to the module to be added

Definition at line 171 of file MaCh3Program.cpp.

171  {
172  MaCh3ArgumentParser* parser = module.get_parser();
173  this->add_subparser(*parser);
174  m_module_map[parser] = &module;
175  m_subcommands.push_back(parser->name());
176  }
std::vector< std::string > m_subcommands
List of registered subcommand names.
std::map< const MaCh3ArgumentParser *, IModule * > m_module_map
Map of parsers to core modules (non-owning)

◆ completions()

void M3::MaCh3Program::completions ( const std::string &  prefix,
const std::vector< std::string > &  context 
) const

Generate completion suggestions given the current prefix and preceding words.

Parameters
prefixThe partially-typed word being completed
contextWords already typed after the program name but before prefix

Definition at line 88 of file MaCh3Program.cpp.

88  {
89  // Find the active subcommand from previously typed context words
90  std::string active_subcommand;
91  for (const auto& word : context) {
92  for (const auto& sub : m_subcommands) {
93  if (word == sub) {
94  active_subcommand = sub;
95  break;
96  }
97  }
98  if (!active_subcommand.empty()) break;
99  }
100 
101  if (active_subcommand.empty()) {
102  // No subcommand typed yet — complete top-level subcommand names
103  for (const auto& cmd : m_subcommands) {
104  if (cmd.rfind(prefix, 0) == 0)
105  std::cout << cmd << "\n";
106  }
107  return;
108  }
109 
110  // Find the parser for the active subcommand
111  const MaCh3ArgumentParser* sub_parser = nullptr;
112  for (const auto& [parser, mod] : m_module_map) {
113  if (parser->name() == active_subcommand) { sub_parser = parser; break; }
114  }
115  if (!sub_parser) {
116  for (const auto& [parser, plug] : m_dynamic_plugin_map) {
117  if (parser->name() == active_subcommand) { sub_parser = parser; break; }
118  }
119  }
120  if (!sub_parser) return; // unknown subcommand — fall back to files
121 
122  // If the last context word is an option that expects a value argument,
123  // output nothing so the shell falls back to file completion.
124  if (!context.empty()) {
125  const std::string& prev = context.back();
126  if (prev.size() >= 2 && prev[0] == '-') {
127  if (sub_parser->option_takes_value(prev)) {
128  return; // option takes a value — let the shell complete files
129  }
130  }
131  }
132 
133  // Complete long option names when the prefix starts with '-'
134  if (!prefix.empty() && prefix[0] == '-') {
135  for (const auto& name : sub_parser->get_long_option_names()) {
136  if (name.rfind(prefix, 0) == 0)
137  std::cout << name << "\n";
138  }
139  return;
140  }
141  // Non-option prefix (or empty) — output nothing, shell falls back to files
142  }
const std::string name() const
Get the name of this parser/subcommand.
Definition: m3argparse.hpp:24
std::map< const MaCh3ArgumentParser *, std::unique_ptr< DynamicPlugin > > m_dynamic_plugin_map
Map of parsers to dynamic plugins (owning)

◆ detect_shell()

std::string M3::MaCh3Program::detect_shell ( ) const
private

Detect the user's shell from the SHELL environment variable.

Returns
Shell name ("bash", "zsh", or empty string if not detected)

Definition at line 28 of file MaCh3Program.cpp.

28  {
29  const char* shell = std::getenv("SHELL");
30  if (!shell) return "";
31 
32  std::string s(shell);
33  if (s.find("bash") != std::string::npos) return "bash";
34  if (s.find("zsh") != std::string::npos) return "zsh";
35 
36  return "";
37  }

◆ expand_plugin_path()

std::vector< std::string > M3::MaCh3Program::expand_plugin_path ( const std::string &  path) const
private

Expand a plugin path (file or directory) into a list of .so files.

Expand a plugin path to a list of shared library files.

Parameters
pathPath to a plugin file or directory containing plugins
Returns
Vector of plugin file paths

If the path is a file, returns it directly. If it's a directory, returns all .so files found in that directory.

Parameters
pathPath to a plugin file or directory
Returns
Vector of absolute paths to .so files

Definition at line 255 of file MaCh3Program.cpp.

255  {
256  std::vector<std::string> result;
257 
258  fs::path p(path);
259 
260  if (!fs::exists(p)) {
261  std::cerr << "Plugin path does not exist: " << path << "\n";
262  return result;
263  }
264 
265  if (fs::is_regular_file(p)) {
266  // Single .so file
267  result.push_back(path);
268  }
269  else if (fs::is_directory(p)) {
270  // Load all .so files in directory
271  for (auto& entry : fs::directory_iterator(p)) {
272  if (entry.is_regular_file() && entry.path().extension() == ".so") {
273  result.push_back(entry.path().string());
274  }
275  }
276  }
277  else {
278  std::cerr << "Invalid plugin path (not file or directory): " << path << "\n";
279  }
280 
281  return result;
282  }

◆ install_completions()

void M3::MaCh3Program::install_completions ( ) const

Install shell completion scripts for bash and zsh.

Definition at line 39 of file MaCh3Program.cpp.

39  {
40  const char* home = std::getenv("HOME");
41  if (!home) {
42  std::cerr << "Cannot determine HOME directory\n";
43  return;
44  }
45 
46  std::string shell = detect_shell();
47  if (shell.empty()) {
48  std::cerr << "Could not detect your shell. Supported: bash, zsh\n";
49  return;
50  }
51 
52  // Resolve the absolute path of this binary so the completion script
53  // works even when mach3 is not on PATH.
54  std::string binary_path = "mach3";
55  try {
56  binary_path = fs::read_symlink("/proc/self/exe").string();
57  } catch (...) {}
58 
59  // Replace the generic "mach3" invocation in the template with the
60  // absolute binary path so the script is self-contained.
61  auto embed_path = [&](std::string_view tmpl) -> std::string {
62  std::string s(tmpl);
63  const std::string placeholder = "mach3 --complete";
64  const std::string replacement = binary_path + " --complete";
65  for (std::size_t pos = s.find(placeholder); pos != std::string::npos;
66  pos = s.find(placeholder, pos + replacement.size()))
67  s.replace(pos, placeholder.size(), replacement);
68  return s;
69  };
70 
71  fs::path home_path(home);
72 
73  if (shell == "bash") {
74  fs::path path = home_path / ".local/share/bash-completion/completions/mach3";
75  if (write_file(path, embed_path(MaCh3Program::BASH_COMPLETION)))
76  std::cout << "Installed bash completions → " << path << "\n";
77  return;
78  }
79 
80  if (shell == "zsh") {
81  fs::path path = home_path / ".local/share/zsh/site-functions/_mach3";
82  if (write_file(path, embed_path(MaCh3Program::ZSH_COMPLETION)))
83  std::cout << "Installed zsh completions → " << path << "\n";
84  return;
85  }
86  }
bool write_file(const fs::path &path, std::string_view content) const
Write content to a file, creating parent directories if needed.
static constexpr std::string_view ZSH_COMPLETION
static constexpr std::string_view BASH_COMPLETION
std::string detect_shell() const
Detect the user's shell from the SHELL environment variable.

◆ load_dynamic_plugins()

void M3::MaCh3Program::load_dynamic_plugins ( )

Load dynamic plugins from the MACH3_PLUGINS environment variable.

Load dynamic plugins from paths specified in MACH3_PLUGINS environment variable.

Reads the MACH3_PLUGINS environment variable (colon-separated paths), loads each .so file, and registers the plugins with the program. Each plugin must export create_plugin and destroy_plugin functions.

Definition at line 183 of file MaCh3Program.cpp.

183  {
184  const char* env = std::getenv("MACH3_PLUGINS");
185  if (!env) {
186  //std::cerr << "No plugins specified\n";
187  return;
188  }
189 
190  std::stringstream ss(env);
191  std::string path;
192 
193  while (std::getline(ss, path, ':')) {
194  for (const auto& sofile : this->expand_plugin_path(path)) {
195  try{
196  std::unique_ptr<DynamicPlugin> dlplugin = std::make_unique<DynamicPlugin>(sofile);
197  MaCh3ArgumentParser* parser = dlplugin->get_parser();
198  if (!parser) {
199  std::cerr << "Plugin " << sofile << " did not provide a valid parser.\n";
200  continue;
201  }
202 
203  this->add_subparser(*parser);
204  m_dynamic_plugin_map[parser] = std::move(dlplugin);
205  m_subcommands.push_back(parser->name());
206  }
207  catch (const std::exception& e) {
208  std::cerr << "Failed to load plugin from " << sofile << ": " << e.what() << "\n";
209  continue;
210  }
211  catch (...) {
212  std::cerr << "Failed to load plugin from " << sofile << ": unknown error\n";
213  continue;
214  }
215  }
216  }
217  }
std::vector< std::string > expand_plugin_path(const std::string &path) const
Expand a plugin path (file or directory) into a list of .so files.

◆ Run()

int M3::MaCh3Program::Run ( )

Run the selected subcommand.

Execute the selected subcommand.

Returns
Exit code from the executed module

Determines which subcommand was invoked and runs the corresponding module or plugin.

Returns
Exit code from the executed module (0 on success)

Definition at line 231 of file MaCh3Program.cpp.

231  {
232  const MaCh3ArgumentParser& sub_parser = this->get_subcommand_used();
233 
234  if (sub_parser){
235  auto plugin_itr = m_module_map.find(&sub_parser);
236  if (plugin_itr != m_module_map.end()){
237  return plugin_itr->second->Run();
238  }
239  auto dplugin_itr = m_dynamic_plugin_map.find(&sub_parser);
240  if (dplugin_itr != m_dynamic_plugin_map.end()){
241  return dplugin_itr->second->Run();
242  }
243  }
244  return 0;
245  }
const MaCh3ArgumentParser & get_subcommand_used() const
Get the subcommand that was used in parsing.
Definition: m3argparse.hpp:40

◆ unload_dynamic_plugins()

void M3::MaCh3Program::unload_dynamic_plugins ( )

Unload all dynamically loaded plugins.

Unload all dynamic plugins and clean up resources.

Clears the plugin map. Smart pointers automatically handle cleanup.

Definition at line 222 of file MaCh3Program.cpp.

222  {
223  m_dynamic_plugin_map.clear();
224  }

◆ write_file()

bool M3::MaCh3Program::write_file ( const fs::path &  path,
std::string_view  content 
) const
private

Write content to a file, creating parent directories if needed.

Parameters
pathFilesystem path to write to
contentContent to write to the file
Returns
true if successful, false otherwise

Definition at line 16 of file MaCh3Program.cpp.

16  {
17  try {
18  fs::create_directories(path.parent_path());
19  std::ofstream out(path);
20  if (!out) return false;
21  out << content;
22  return true;
23  } catch (...) {
24  return false;
25  }
26  }

Member Data Documentation

◆ BASH_COMPLETION

constexpr std::string_view M3::MaCh3Program::BASH_COMPLETION
staticconstexprprivate
Initial value:
= R"(
_mach3_complete() {
local cur prev words cword
_init_completion || return
# Pass current prefix + preceding words as context; binary filters by prefix
local -a raw
mapfile -t raw < <(mach3 --complete "$cur" "${words[@]:1:$((cword-1))}" 2>/dev/null)
if [[ ${#raw[@]} -eq 0 ]]; then
# No completions from binary — fall back to default (file) completion
compopt -o default 2>/dev/null
return
fi
COMPREPLY=( "${raw[@]}" )
}
complete -F _mach3_complete mach3
)"

Definition at line 78 of file MaCh3Program.hpp.

◆ m_dynamic_plugin_map

std::map<const MaCh3ArgumentParser*, std::unique_ptr<DynamicPlugin> > M3::MaCh3Program::m_dynamic_plugin_map
private

Map of parsers to dynamic plugins (owning)

Definition at line 76 of file MaCh3Program.hpp.

◆ m_module_map

std::map<const MaCh3ArgumentParser*, IModule*> M3::MaCh3Program::m_module_map
private

Map of parsers to core modules (non-owning)

Definition at line 75 of file MaCh3Program.hpp.

◆ m_subcommands

std::vector<std::string> M3::MaCh3Program::m_subcommands
private

List of registered subcommand names.

Definition at line 74 of file MaCh3Program.hpp.

◆ ZSH_COMPLETION

constexpr std::string_view M3::MaCh3Program::ZSH_COMPLETION
staticconstexprprivate
Initial value:
= R"(
#compdef mach3
_mach3() {
local cur="${words[$CURRENT]}"
local -a ctx
ctx=("${words[@]:1:$((CURRENT-2))}")
local -a completions
completions=("${(@f)$(mach3 --complete "$cur" "${ctx[@]}" 2>/dev/null)}")
if [[ ${#completions[@]} -eq 0 ]]; then
_files
return
fi
compadd -a completions
}
_mach3
)"

Definition at line 96 of file MaCh3Program.hpp.


The documentation for this class was generated from the following files: