blob: dbd1bcde0f52f665b837428d9c37cac220052540 [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
Austin Schuh8e2dfc62022-08-17 16:36:00 -07009DEFINE_string(config, "aos_config.json", "File path of aos configuration");
Brian Silvermanea2c95f2021-02-10 18:10:26 -080010
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,
Austin Schuh59f3b0f2021-07-31 20:50:40 -070035 std::function<bool(const aos::Channel *)> channel_filter,
Austin Schuhba2c8652022-08-17 14:56:08 -070036 std::string_view channel_filter_description, bool expect_args) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080037 // Don't generate failure output if the config doesn't exist while attempting
38 // to autocomplete.
Milind Upadhyay17098ba2022-04-15 22:18:50 -070039 if (FLAGS__bash_autocomplete &&
40 (!(EndsWith(FLAGS_config, ".json") || EndsWith(FLAGS_config, ".bfbs")))) {
Brian Silvermanea2c95f2021-02-10 18:10:26 -080041 std::cout << "COMPREPLY=()";
42 return true;
43 }
44
Milind Upadhyay17098ba2022-04-15 22:18:50 -070045 config = aos::configuration::MaybeReadConfig(FLAGS_config);
46 if (FLAGS__bash_autocomplete && !config.has_value()) {
47 std::cout << "COMPREPLY=()";
48 return true;
49 }
50 CHECK(config.has_value()) << "Could not read config. See above errors.";
51
Brian Silvermanea2c95f2021-02-10 18:10:26 -080052 event_loop.emplace(&config->message());
53 event_loop->SkipTimingReport();
54 event_loop->SkipAosLog();
55
Austin Schuhd30a5ca2021-07-31 20:49:35 -070056 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Brian Silvermanea2c95f2021-02-10 18:10:26 -080057 event_loop->configuration()->channels();
Austin Schuhd30a5ca2021-07-31 20:49:35 -070058
59 do {
60 std::string channel_name;
61 std::string message_type;
62 if (*argc > 1) {
63 channel_name = (*argv)[1];
64 ShiftArgs(argc, argv);
65 }
66 if (*argc > 1) {
67 message_type = (*argv)[1];
68 ShiftArgs(argc, argv);
69 }
70
71 if (FLAGS__bash_autocomplete) {
72 Autocomplete(channel_name, message_type, channel_filter);
73 return true;
74 }
75
76 if (channel_name.empty() && message_type.empty()) {
77 std::cout << "Channels:\n";
78 for (const aos::Channel *channel : *channels) {
79 if (FLAGS_all || channel_filter(channel)) {
80 std::cout << channel->name()->c_str() << ' '
81 << channel->type()->c_str() << '\n';
82 }
83 }
84 return true;
85 }
86
87 std::vector<const aos::Channel *> found_channels_now;
88 bool found_exact = false;
Brian Silvermanea2c95f2021-02-10 18:10:26 -080089 for (const aos::Channel *channel : *channels) {
Austin Schuhd30a5ca2021-07-31 20:49:35 -070090 if (channel->name()->c_str() != channel_name) {
91 continue;
Brian Silvermanea2c95f2021-02-10 18:10:26 -080092 }
Austin Schuhba2c8652022-08-17 14:56:08 -070093
Austin Schuhd30a5ca2021-07-31 20:49:35 -070094 if (channel->type()->string_view() == message_type) {
95 if (!found_exact) {
96 found_channels_now.clear();
97 found_exact = true;
98 }
99 } else if (!found_exact && channel->type()->string_view().find(
100 message_type) != std::string_view::npos) {
101 } else {
102 continue;
103 }
Austin Schuhba2c8652022-08-17 14:56:08 -0700104
105 if (!FLAGS_all && !channel_filter(channel)) {
106 LOG(FATAL) << "matched channel does not pass the channel filter: \""
107 << channel_filter_description
108 << "\" [matched channel info]: "
109 << configuration::CleanedChannelToString(channel);
110 }
111
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700112 found_channels_now.push_back(channel);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800113 }
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800114
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700115 if (found_channels_now.empty()) {
116 LOG(FATAL)
117 << "Could not find any channels with the given name and type for "
118 << channel_name << " " << message_type;
119 } else if (found_channels_now.size() > 1 && !message_type.empty()) {
120 LOG(FATAL) << "Multiple channels found with same type for "
121 << channel_name << " " << message_type;
122 }
123 for (const aos::Channel *channel : found_channels_now) {
124 found_channels.push_back(channel);
125 }
Austin Schuh59f3b0f2021-07-31 20:50:40 -0700126 } while (expect_args && *argc > 1);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800127
128 return false;
129}
130
131void CliUtilInfo::Autocomplete(
132 std::string_view channel_name, std::string_view message_type,
133 std::function<bool(const aos::Channel *)> channel_filter) {
134 const aos::Configuration *const config_msg = event_loop->configuration();
135 const bool unique_match =
136 std::count_if(config_msg->channels()->begin(),
137 config_msg->channels()->end(),
138 [channel_name, message_type](const aos::Channel *channel) {
139 return channel->name()->string_view() == channel_name &&
140 channel->type()->string_view() == message_type;
141 }) == 1;
142
143 const bool editing_message =
144 !channel_name.empty() && FLAGS__bash_autocomplete_word == message_type;
145 const bool editing_channel =
146 !editing_message && FLAGS__bash_autocomplete_word == channel_name;
147
148 std::cout << "COMPREPLY=(";
149
150 // If we have a unique match, don't provide any suggestions. Otherwise, check
151 // that were're editing one of the two positional arguments.
152 if (!unique_match && (editing_message || editing_channel)) {
153 for (const aos::Channel *channel : *config_msg->channels()) {
154 if (FLAGS_all || channel_filter(channel)) {
155 // Suggest only message types if the message type argument is being
156 // entered.
157 if (editing_message) {
158 // Then, filter for only channel names that match exactly and types
159 // that begin with message_type.
160 if (channel->name()->string_view() == channel_name &&
161 channel->type()->string_view().find(message_type) == 0) {
162 std::cout << '\'' << channel->type()->c_str() << "' ";
163 }
164 } else if (channel->name()->string_view().find(channel_name) == 0) {
165 // If the message type empty, then return full autocomplete.
166 // Otherwise, since the message type is poulated yet not being edited,
167 // the user must be editing the channel name alone, in which case only
168 // suggest channel names, not pairs.
169 if (message_type.empty()) {
170 std::cout << '\'' << channel->name()->c_str() << ' '
171 << channel->type()->c_str() << "' ";
172 } else {
173 std::cout << '\'' << channel->name()->c_str() << "' ";
174 }
175 }
176 }
177 }
178 }
179 std::cout << ')';
180}
181
182} // namespace aos