blob: e36c3d2187e18c976b862e81e2fe88ad484ef419 [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
47 std::string channel_name;
48 std::string message_type;
49 if (*argc > 1) {
50 channel_name = (*argv)[1];
51 ShiftArgs(argc, argv);
52 }
53 if (*argc > 1) {
54 message_type = (*argv)[1];
55 ShiftArgs(argc, argv);
56 }
57
58 config.emplace(aos::configuration::ReadConfig(FLAGS_config));
59 event_loop.emplace(&config->message());
60 event_loop->SkipTimingReport();
61 event_loop->SkipAosLog();
62
63 if (FLAGS__bash_autocomplete) {
64 Autocomplete(channel_name, message_type, channel_filter);
65 return true;
66 }
67
68 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *const channels =
69 event_loop->configuration()->channels();
70 if (channel_name.empty() && message_type.empty()) {
71 std::cout << "Channels:\n";
72 for (const aos::Channel *channel : *channels) {
73 if (FLAGS_all || channel_filter(channel)) {
74 std::cout << channel->name()->c_str() << ' ' << channel->type()->c_str()
75 << '\n';
76 }
77 }
78 return true;
79 }
80
81 bool found_exact = false;
82 for (const aos::Channel *channel : *channels) {
83 if (!FLAGS_all && !channel_filter(channel)) {
84 continue;
85 }
86 if (channel->name()->c_str() != channel_name) {
87 continue;
88 }
89 if (channel->type()->string_view() == message_type) {
90 if (!found_exact) {
91 found_channels.clear();
92 found_exact = true;
93 }
94 } else if (!found_exact && channel->type()->string_view().find(
95 message_type) != std::string_view::npos) {
96 } else {
97 continue;
98 }
99 found_channels.push_back(channel);
100 }
101
102 if (found_channels.empty()) {
103 LOG(FATAL) << "Could not find any channels with the given name and type.";
104 } else if (found_channels.size() > 1 && !message_type.empty()) {
105 LOG(FATAL) << "Multiple channels found with same type";
106 }
107
108 return false;
109}
110
111void CliUtilInfo::Autocomplete(
112 std::string_view channel_name, std::string_view message_type,
113 std::function<bool(const aos::Channel *)> channel_filter) {
114 const aos::Configuration *const config_msg = event_loop->configuration();
115 const bool unique_match =
116 std::count_if(config_msg->channels()->begin(),
117 config_msg->channels()->end(),
118 [channel_name, message_type](const aos::Channel *channel) {
119 return channel->name()->string_view() == channel_name &&
120 channel->type()->string_view() == message_type;
121 }) == 1;
122
123 const bool editing_message =
124 !channel_name.empty() && FLAGS__bash_autocomplete_word == message_type;
125 const bool editing_channel =
126 !editing_message && FLAGS__bash_autocomplete_word == channel_name;
127
128 std::cout << "COMPREPLY=(";
129
130 // If we have a unique match, don't provide any suggestions. Otherwise, check
131 // that were're editing one of the two positional arguments.
132 if (!unique_match && (editing_message || editing_channel)) {
133 for (const aos::Channel *channel : *config_msg->channels()) {
134 if (FLAGS_all || channel_filter(channel)) {
135 // Suggest only message types if the message type argument is being
136 // entered.
137 if (editing_message) {
138 // Then, filter for only channel names that match exactly and types
139 // that begin with message_type.
140 if (channel->name()->string_view() == channel_name &&
141 channel->type()->string_view().find(message_type) == 0) {
142 std::cout << '\'' << channel->type()->c_str() << "' ";
143 }
144 } else if (channel->name()->string_view().find(channel_name) == 0) {
145 // If the message type empty, then return full autocomplete.
146 // Otherwise, since the message type is poulated yet not being edited,
147 // the user must be editing the channel name alone, in which case only
148 // suggest channel names, not pairs.
149 if (message_type.empty()) {
150 std::cout << '\'' << channel->name()->c_str() << ' '
151 << channel->type()->c_str() << "' ";
152 } else {
153 std::cout << '\'' << channel->name()->c_str() << "' ";
154 }
155 }
156 }
157 }
158 }
159 std::cout << ')';
160}
161
162} // namespace aos