aboutsummaryrefslogtreecommitdiffstats
path: root/liblfjail/command_map.cc
blob: 82b2fe05e478cfefffca9461d4aa4e1cdd613ed0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
 * This source code is released into the public domain.
 */

#include <map>
#include <print>

#include "command_map.hh"

namespace lfjail::command {

namespace {

// A node in the command hierarchy.
struct node {
	// The command name of this node.
	std::string command;

	// Handler for this node.  May be null, which means this node has
	// sub-commands but isn't a command itself.
	detail::command_base *handler = nullptr;

	node(std::string command);

	// Run the handler for this node.
	auto invoke(context const &ctx, int argc, char **argv) const -> int;

	// Create a new node under this one, or return it if it already exists.
	// If path is empty, return this node.
	auto create_node(std::string_view path) -> node&;

	auto print_usage(std::string prefix) const -> void;

private:
	std::map<std::string_view, node> commands;
};

node::node(std::string command_)
	: command(std::move(command_))
{}

auto node::invoke(context const &ctx, int argc, char **argv) const -> int {
	// Look for a subcommand with argv[0].
	auto it = commands.find(argv[0]);
	if (it == commands.end()) {
		std::print(std::cerr, "{}: unknown command\n", ::getprogname());
		throw usage_error();
	}

	auto const &child = it->second;

	// If the child has a handler, invoke it.
	if (child.handler != nullptr)
		return child.handler->invoke(ctx, argc, argv);

	--argc;
	++argv;

	if (argc == 0) {
		std::print(std::cerr, "{}: incomplete command\n", ::getprogname());
		throw usage_error();
	}

	// Otherwise, continue recursing.
	return child.invoke(ctx, argc, argv);
}

auto node::create_node(std::string_view path) -> node& {
	auto child = next_word(&path);
	if (child.empty())
		return *this;

	// If the child node doesn't exist, insert an empty node.
	auto it = commands.find(child);
	if (it == commands.end()) {
		std::tie(it, std::ignore) =
			commands.insert(std::pair{child, node(std::string(child))});
	}

	if (path.empty())
		return it->second;

	return it->second.create_node(path);
}

void node::print_usage(std::string prefix) const {
	if (handler != nullptr)
		std::print("{}{}\n", prefix, command);

	for (auto const &it : commands)
		it.second.print_usage(prefix + command + " ");
}

// This may be called before main(), so catch any exceptions.
auto get_root_node() noexcept -> node& try {
	static node root_node("");
	return root_node;
} catch (std::exception const &exc) {
	std::cerr << "lfjail: ERROR: get_root_node: " << exc.what() << "\n";
	std::exit(1);
}

} // anonymous namespace

auto dispatch(context const &ctx, int argc, char **argv) -> int {
	return get_root_node().invoke(ctx, argc, argv);
}

void print_usage(std::string_view prefix) {
	get_root_node().print_usage(std::string(prefix));
}

namespace detail {

/*
 * This is usually called at global scope before main() runs, so don't
 * propagate exceptions.
 */
command_base::command_base(std::string_view path) noexcept
try {
	auto &node = get_root_node().create_node(path);
	node.handler = this;
} catch (std::exception const &exc) {
	std::cerr << "lfjail: ERROR: failed to initialise command "
		<< path << ": " << exc.what() << "\n";
	std::abort();
}

}

} // namespace lfjail