blob: 56210eeb79f3b92f9c2b68451e7b7eae88d083ce [file] [log] [blame]
Brian Silvermanea2c95f2021-02-10 18:10:26 -08001#include "aos/aos_cli_utils.h"
2
3#include <sys/stat.h>
4#include <sys/types.h>
5#include <unistd.h>
6
7#include <iostream>
8
9DEFINE_string(config, "./config.json", "File path of aos configuration");
10
11DEFINE_bool(
12 _bash_autocomplete, false,
13 "Internal use: Outputs channel list for use with autocomplete script.");
14DEFINE_string(_bash_autocomplete_word, "",
15 "Internal use: Current word being autocompleted");
16
17DEFINE_bool(all, false,
18 "If true, print out the channels for all nodes, not just the "
19 "channels which are visible on this node.");
20
21namespace aos {
22namespace {
23
24bool EndsWith(std::string_view str, std::string_view ending) {
25 const std::size_t offset = str.size() - ending.size();
26 return str.size() >= ending.size() &&
27 std::equal(str.begin() + offset, str.end(), ending.begin(),
28 ending.end());
29}
30
31} // namespace
32
33bool CliUtilInfo::Initialize(
34 int *argc, char ***argv,
35 std::function<bool(const aos::Channel *)> channel_filter) {
36 // Don't generate failure output if the config doesn't exist while attempting
37 // to autocomplete.
38 if (struct stat file_stat;
39 FLAGS__bash_autocomplete &&
40 (!(EndsWith(FLAGS_config, ".json") || EndsWith(FLAGS_config, ".bfbs")) ||
41 stat(FLAGS_config.c_str(), &file_stat) != 0 ||
42 (file_stat.st_mode & S_IFMT) != S_IFREG)) {
43 std::cout << "COMPREPLY=()";
44 return true;
45 }
46
Brian Silvermanea2c95f2021-02-10 18:10:26 -080047 config.emplace(aos::configuration::ReadConfig(FLAGS_config));
48 event_loop.emplace(&config->message());
49 event_loop->SkipTimingReport();
50 event_loop->SkipAosLog();
51
Austin Schuhd30a5ca2021-07-31 20:49:35 -070052 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Brian Silvermanea2c95f2021-02-10 18:10:26 -080053 event_loop->configuration()->channels();
Austin Schuhd30a5ca2021-07-31 20:49:35 -070054
55 do {
56 std::string channel_name;
57 std::string message_type;
58 if (*argc > 1) {
59 channel_name = (*argv)[1];
60 ShiftArgs(argc, argv);
61 }
62 if (*argc > 1) {
63 message_type = (*argv)[1];
64 ShiftArgs(argc, argv);
65 }
66
67 if (FLAGS__bash_autocomplete) {
68 Autocomplete(channel_name, message_type, channel_filter);
69 return true;
70 }
71
72 if (channel_name.empty() && message_type.empty()) {
73 std::cout << "Channels:\n";
74 for (const aos::Channel *channel : *channels) {
75 if (FLAGS_all || channel_filter(channel)) {
76 std::cout << channel->name()->c_str() << ' '
77 << channel->type()->c_str() << '\n';
78 }
79 }
80 return true;
81 }
82
83 std::vector<const aos::Channel *> found_channels_now;
84 bool found_exact = false;
Brian Silvermanea2c95f2021-02-10 18:10:26 -080085 for (const aos::Channel *channel : *channels) {
Austin Schuhd30a5ca2021-07-31 20:49:35 -070086 if (!FLAGS_all && !channel_filter(channel)) {
87 continue;
Brian Silvermanea2c95f2021-02-10 18:10:26 -080088 }
Austin Schuhd30a5ca2021-07-31 20:49:35 -070089 if (channel->name()->c_str() != channel_name) {
90 continue;
Brian Silvermanea2c95f2021-02-10 18:10:26 -080091 }
Austin Schuhd30a5ca2021-07-31 20:49:35 -070092 if (channel->type()->string_view() == message_type) {
93 if (!found_exact) {
94 found_channels_now.clear();
95 found_exact = true;
96 }
97 } else if (!found_exact && channel->type()->string_view().find(
98 message_type) != std::string_view::npos) {
99 } else {
100 continue;
101 }
102 found_channels_now.push_back(channel);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800103 }
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800104
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700105 if (found_channels_now.empty()) {
106 LOG(FATAL)
107 << "Could not find any channels with the given name and type for "
108 << channel_name << " " << message_type;
109 } else if (found_channels_now.size() > 1 && !message_type.empty()) {
110 LOG(FATAL) << "Multiple channels found with same type for "
111 << channel_name << " " << message_type;
112 }
113 for (const aos::Channel *channel : found_channels_now) {
114 found_channels.push_back(channel);
115 }
116 } while (*argc > 1);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800117
118 return false;
119}
120
121void CliUtilInfo::Autocomplete(
122 std::string_view channel_name, std::string_view message_type,
123 std::function<bool(const aos::Channel *)> channel_filter) {
124 const aos::Configuration *const config_msg = event_loop->configuration();
125 const bool unique_match =
126 std::count_if(config_msg->channels()->begin(),
127 config_msg->channels()->end(),
128 [channel_name, message_type](const aos::Channel *channel) {
129 return channel->name()->string_view() == channel_name &&
130 channel->type()->string_view() == message_type;
131 }) == 1;
132
133 const bool editing_message =
134 !channel_name.empty() && FLAGS__bash_autocomplete_word == message_type;
135 const bool editing_channel =
136 !editing_message && FLAGS__bash_autocomplete_word == channel_name;
137
138 std::cout << "COMPREPLY=(";
139
140 // If we have a unique match, don't provide any suggestions. Otherwise, check
141 // that were're editing one of the two positional arguments.
142 if (!unique_match && (editing_message || editing_channel)) {
143 for (const aos::Channel *channel : *config_msg->channels()) {
144 if (FLAGS_all || channel_filter(channel)) {
145 // Suggest only message types if the message type argument is being
146 // entered.
147 if (editing_message) {
148 // Then, filter for only channel names that match exactly and types
149 // that begin with message_type.
150 if (channel->name()->string_view() == channel_name &&
151 channel->type()->string_view().find(message_type) == 0) {
152 std::cout << '\'' << channel->type()->c_str() << "' ";
153 }
154 } else if (channel->name()->string_view().find(channel_name) == 0) {
155 // If the message type empty, then return full autocomplete.
156 // Otherwise, since the message type is poulated yet not being edited,
157 // the user must be editing the channel name alone, in which case only
158 // suggest channel names, not pairs.
159 if (message_type.empty()) {
160 std::cout << '\'' << channel->name()->c_str() << ' '
161 << channel->type()->c_str() << "' ";
162 } else {
163 std::cout << '\'' << channel->name()->c_str() << "' ";
164 }
165 }
166 }
167 }
168 }
169 std::cout << ')';
170}
171
172} // namespace aos