LCOV - code coverage report
Current view: top level - src/cli - command_node.cpp (source / functions) Coverage Total Hit
Test: HPActor Coverage Lines: 100.0 % 64 64
Test Date: 2026-05-20 02:24:49 Functions: 100.0 % 7 7
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             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
        

Generated by: LCOV version 2.0-1