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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
// This source code is released into the public domain.
export module nihil.cli:command_tree;
import nihil.std;
import nihil.generator;
import nihil.util;
import :command;
import :registry;
namespace nihil {
// command_tree_node represents a node in the command tree. Each node contains a command, which is
// either a real command which can be invoked, or a stub command which acts as a placeholder in the
// tree. For example, if two commands "add foo" and "add bar" are defined, then "add" will be
// implicitly created as a stub command.
struct command_tree_node final
{
// Create an empty node with no parent. This is used as the root of the tree.
command_tree_node()
: m_command(std::make_shared<nihil::command>(""))
{
}
// Create a node that contains a command and has a parent.
command_tree_node(command_tree_node *parent, std::string_view const this_word,
std::shared_ptr<nihil::command> command)
: m_parent(parent)
, m_this_word(this_word)
, m_command(std::move(command))
{
}
// Return a child node, or NULL if the child doesn't exist.
[[nodiscard]] auto get_child(this command_tree_node const &self,
std::string_view const child) -> command_tree_node const *
{
if (auto it = self.m_children.find(std::string(child)); it != self.m_children.end())
return &it->second;
return nullptr;
}
[[nodiscard]] auto
get_child(this command_tree_node &self, std::string_view const child) -> command_tree_node *
{
if (auto it = self.m_children.find(std::string(child)); it != self.m_children.end())
return &it->second;
return nullptr;
}
// Return a child node if it exists, or insert a new empty node.
[[nodiscard]] auto get_or_create_child(this command_tree_node &self, std::string_view child)
-> command_tree_node *
{
// Return the existing child, if there is one.
if (auto *ptr = self.get_child(child); ptr != nullptr)
return ptr;
// Create a new node with a dummy command.
auto path = self.m_parent != nullptr
? std::format("{} {}", self.m_parent->command().path(), child)
: std::string(child);
auto node = command_tree_node(&self, child, std::make_shared<nihil::command>(path));
auto [it, ok] = self.m_children.emplace(child, node);
if (!ok)
throw std::logic_error("failed to insert command tree node");
return &it->second;
}
// Get node's command.
[[nodiscard]] auto command(this command_tree_node const &self) -> command &
{
return *self.m_command;
}
auto command(this command_tree_node &self, std::shared_ptr<nihil::command> command) -> void
{
// If we have a stub command, allow it to be replaced. This happens when
// a stub node is replaced with a real node during tree building. However,
// if we already have an invocable command, this is an error.
if (self.m_command->invocable())
throw std::logic_error("duplicate command");
self.m_command = std::move(command);
}
// Yield all children of this node, depth-first.
auto
all_children(this command_tree_node const &self) -> generator<command_tree_node const &>
{
for (auto &&node : self.m_children | std::views::values) {
co_yield node;
co_yield elements_of(node.all_children());
}
}
// Yield direct children in this node.
auto children(this command_tree_node const &self)
{
return self.m_children | std::views::values;
}
private:
command_tree_node *m_parent = nullptr;
std::string m_this_word;
std::shared_ptr<nihil::command> m_command;
std::map<std::string, command_tree_node> m_children;
};
// The command tree stores commands in a tree structure suitable for searching.
struct command_tree
{
// Add a node to the tree. Returns false if the node already exists.
auto insert(this command_tree &self, std::vector<std::string_view> const &path,
std::shared_ptr<command> command) -> void
{
auto *this_node = &self.m_root_node;
// Find the node for this key.
for (auto &&this_word : path)
this_node = this_node->get_or_create_child(this_word);
// Set the new value.
this_node->command(std::move(command));
}
// Find a node in the tree.
[[nodiscard]] auto
find(this command_tree const &self, int &argc, char **&argv) -> command_tree_node const *
{
auto const *this_node = &self.m_root_node;
// Iterate until we don't find a child command, then return that node.
while (argv[0] != nullptr) {
auto const *next_node = this_node->get_child(argv[0]);
if (next_node == nullptr)
return this_node;
this_node = next_node;
--argc;
++argv;
}
// We ran out of path without finding a valid command. Return this
// node; the caller will notice the missing command.
return this_node;
}
private:
command_tree_node m_root_node;
};
// Build a command tree from the registry.
[[nodiscard]] auto build_command_tree() -> command_tree
{
auto tree = command_tree();
for (auto &&command : get_registered_commands()) {
auto split_path = command->path() //
| std::views::split(' ') //
| std::views::transform(construct<std::string_view>) //
| std::ranges::to<std::vector>();
// Throws std::logic_error on duplicates.
tree.insert(split_path, command);
}
return tree;
}
} // namespace nihil
|