blob: f71ed2ac20d5d2370275dcff7b0edc00e4d74467 [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 Schuhc5fa6d92022-02-25 14:36:28 -08009DEFINE_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,
36 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.
39 if (struct stat file_stat;
40 FLAGS__bash_autocomplete &&
41 (!(EndsWith(FLAGS_config, ".json") || EndsWith(FLAGS_config, ".bfbs")) ||
42 stat(FLAGS_config.c_str(), &file_stat) != 0 ||
43 (file_stat.st_mode & S_IFMT) != S_IFREG)) {
44 std::cout << "COMPREPLY=()";
45 return true;
46 }
47
Brian Silvermanea2c95f2021-02-10 18:10:26 -080048 config.emplace(aos::configuration::ReadConfig(FLAGS_config));
49 event_loop.emplace(&config->message());
50 event_loop->SkipTimingReport();
51 event_loop->SkipAosLog();
52
Austin Schuhd30a5ca2021-07-31 20:49:35 -070053 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Brian Silvermanea2c95f2021-02-10 18:10:26 -080054 event_loop->configuration()->channels();
Austin Schuhd30a5ca2021-07-31 20:49:35 -070055
56 do {
57 std::string channel_name;
58 std::string message_type;
59 if (*argc > 1) {
60 channel_name = (*argv)[1];
61 ShiftArgs(argc, argv);
62 }
63 if (*argc > 1) {
64 message_type = (*argv)[1];
65 ShiftArgs(argc, argv);
66 }
67
68 if (FLAGS__bash_autocomplete) {
69 Autocomplete(channel_name, message_type, channel_filter);
70 return true;
71 }
72
73 if (channel_name.empty() && message_type.empty()) {
74 std::cout << "Channels:\n";
75 for (const aos::Channel *channel : *channels) {
76 if (FLAGS_all || channel_filter(channel)) {
77 std::cout << channel->name()->c_str() << ' '
78 << channel->type()->c_str() << '\n';
79 }
80 }
81 return true;
82 }
83
84 std::vector<const aos::Channel *> found_channels_now;
85 bool found_exact = false;
Brian Silvermanea2c95f2021-02-10 18:10:26 -080086 for (const aos::Channel *channel : *channels) {
Austin Schuhd30a5ca2021-07-31 20:49:35 -070087 if (!FLAGS_all && !channel_filter(channel)) {
88 continue;
Brian Silvermanea2c95f2021-02-10 18:10:26 -080089 }
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 Schuhd30a5ca2021-07-31 20:49:35 -070093 if (channel->type()->string_view() == message_type) {
94 if (!found_exact) {
95 found_channels_now.clear();
96 found_exact = true;
97 }
98 } else if (!found_exact && channel->type()->string_view().find(
99 message_type) != std::string_view::npos) {
100 } else {
101 continue;
102 }
103 found_channels_now.push_back(channel);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800104 }
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800105
Austin Schuhd30a5ca2021-07-31 20:49:35 -0700106 if (found_channels_now.empty()) {
107 LOG(FATAL)
108 << "Could not find any channels with the given name and type for "
109 << channel_name << " " << message_type;
110 } else if (found_channels_now.size() > 1 && !message_type.empty()) {
111 LOG(FATAL) << "Multiple channels found with same type for "
112 << channel_name << " " << message_type;
113 }
114 for (const aos::Channel *channel : found_channels_now) {
115 found_channels.push_back(channel);
116 }
Austin Schuh59f3b0f2021-07-31 20:50:40 -0700117 } while (expect_args && *argc > 1);
Brian Silvermanea2c95f2021-02-10 18:10:26 -0800118
119 return false;
120}
121
122void CliUtilInfo::Autocomplete(
123 std::string_view channel_name, std::string_view message_type,
124 std::function<bool(const aos::Channel *)> channel_filter) {
125 const aos::Configuration *const config_msg = event_loop->configuration();
126 const bool unique_match =
127 std::count_if(config_msg->channels()->begin(),
128 config_msg->channels()->end(),
129 [channel_name, message_type](const aos::Channel *channel) {
130 return channel->name()->string_view() == channel_name &&
131 channel->type()->string_view() == message_type;
132 }) == 1;
133
134 const bool editing_message =
135 !channel_name.empty() && FLAGS__bash_autocomplete_word == message_type;
136 const bool editing_channel =
137 !editing_message && FLAGS__bash_autocomplete_word == channel_name;
138
139 std::cout << "COMPREPLY=(";
140
141 // If we have a unique match, don't provide any suggestions. Otherwise, check
142 // that were're editing one of the two positional arguments.
143 if (!unique_match && (editing_message || editing_channel)) {
144 for (const aos::Channel *channel : *config_msg->channels()) {
145 if (FLAGS_all || channel_filter(channel)) {
146 // Suggest only message types if the message type argument is being
147 // entered.
148 if (editing_message) {
149 // Then, filter for only channel names that match exactly and types
150 // that begin with message_type.
151 if (channel->name()->string_view() == channel_name &&
152 channel->type()->string_view().find(message_type) == 0) {
153 std::cout << '\'' << channel->type()->c_str() << "' ";
154 }
155 } else if (channel->name()->string_view().find(channel_name) == 0) {
156 // If the message type empty, then return full autocomplete.
157 // Otherwise, since the message type is poulated yet not being edited,
158 // the user must be editing the channel name alone, in which case only
159 // suggest channel names, not pairs.
160 if (message_type.empty()) {
161 std::cout << '\'' << channel->name()->c_str() << ' '
162 << channel->type()->c_str() << "' ";
163 } else {
164 std::cout << '\'' << channel->name()->c_str() << "' ";
165 }
166 }
167 }
168 }
169 }
170 std::cout << ')';
171}
172
173} // namespace aos