Branch data Line data Source code
1 : : // Copyright 2026 HPActor Contributors
2 : : //
3 : : // Licensed under the Apache License, Version 2.0 (the "License");
4 : : // you may not use this file except in compliance with the License.
5 : : // You may obtain a copy of the License at
6 : : //
7 : : // http://www.apache.org/licenses/LICENSE-2.0
8 : : //
9 : : // Unless required by applicable law or agreed to in writing, software
10 : : // distributed under the License is distributed on an "AS IS" BASIS,
11 : : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : : // See the License for the specific language governing permissions and
13 : : // limitations under the License.
14 : :
15 : : #include <hpactor/cli/command_node.hpp>
16 : : #include <algorithm>
17 : : #include <string>
18 : :
19 : : namespace hpactor {
20 : : namespace cli {
21 : :
22 : 136 : CommandNode* CommandNode::add_child(std::string kw, std::string help, bool is_param) {
23 : 136 : auto node = std::make_unique<CommandNode>();
24 : 136 : node->keyword = std::move(kw);
25 : 136 : node->help_text = std::move(help);
26 : 136 : node->is_parameter = is_param;
27 : 136 : CommandNode* ptr = node.get();
28 : 136 : children.push_back(std::move(node));
29 : 272 : return ptr;
30 : 136 : }
31 : :
32 : 22 : CommandNode* CommandNode::find_child(const std::string& token,
33 : : std::string& param_value) const {
34 : : // Exact keyword match first.
35 : 37 : for (auto& child : children) {
36 : 28 : if (!child->is_parameter && child->keyword == token) {
37 : 13 : return child.get();
38 : : }
39 : : }
40 : : // Parameter match: any child with is_parameter=true matches any token.
41 : 14 : for (auto& child : children) {
42 : 12 : if (child->is_parameter) {
43 : 7 : param_value = token;
44 : 7 : return child.get();
45 : : }
46 : : }
47 : 2 : return nullptr;
48 : : }
49 : :
50 : 10 : static int levenshtein(const std::string& a, const std::string& b) {
51 : 10 : size_t m = a.size(), n = b.size();
52 : : // Small strings — simple DP on stack is fine.
53 : : int d[32][32];
54 : 62 : for (size_t i = 0; i <= m; ++i) d[i][0] = static_cast<int>(i);
55 : 60 : for (size_t j = 0; j <= n; ++j) d[0][j] = static_cast<int>(j);
56 : 52 : for (size_t i = 1; i <= m; ++i) {
57 : 210 : for (size_t j = 1; j <= n; ++j) {
58 : 168 : int cost = (a[i - 1] == b[j - 1]) ? 0 : 1;
59 : 168 : d[i][j] = std::min({d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost});
60 : : }
61 : : }
62 : 10 : return d[m][n];
63 : : }
64 : :
65 : 4 : CommandNode* CommandNode::find_child_prefix(const std::string& prefix) const {
66 : 4 : CommandNode* found = nullptr;
67 : 12 : for (auto& child : children) {
68 : 9 : if (child->is_parameter) continue;
69 : 9 : if (child->keyword.starts_with(prefix)) {
70 : 4 : if (found) return nullptr; // ambiguous — multiple matches
71 : 3 : found = child.get();
72 : : }
73 : : }
74 : 3 : return found;
75 : : }
76 : :
77 : 5 : void CommandNode::collect_completions(const std::string& prefix,
78 : : std::vector<std::string>& out) const {
79 : 18 : for (auto& child : children) {
80 : 13 : if (child->is_parameter) continue;
81 : 11 : if (prefix.empty() || child->keyword.starts_with(prefix)) {
82 : 9 : out.push_back(child->keyword);
83 : : }
84 : : }
85 : 5 : }
86 : :
87 : 5 : std::string CommandNode::suggest(const std::string& token) const {
88 : 5 : std::string best;
89 : 5 : int best_dist = 999;
90 : 15 : for (auto& child : children) {
91 : 10 : if (child->is_parameter) continue;
92 : 10 : int dist = levenshtein(token, child->keyword);
93 : 10 : if (dist <= 2 && dist < best_dist) {
94 : 4 : best_dist = dist;
95 : 4 : best = child->keyword;
96 : : }
97 : : }
98 : 5 : return best;
99 : : }
100 : :
101 : 5 : std::string CommandNode::help(int indent) const {
102 : 5 : std::string out;
103 : 5 : std::string pad(static_cast<size_t>(indent), ' ');
104 : 17 : for (auto& child : children) {
105 : 12 : out += pad;
106 : 12 : if (child->is_parameter) {
107 : 1 : out += "<" + child->keyword + ">";
108 : : } else {
109 : 11 : out += child->keyword;
110 : : }
111 : 12 : out += " — " + child->help_text + "\n";
112 : :
113 : 12 : if (!child->children.empty()) {
114 : 3 : out += child->help(indent + 2);
115 : : }
116 : : }
117 : 5 : return out;
118 : 5 : }
119 : :
120 : : } // namespace cli
121 : : } // namespace hpactor
|