command-definition.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2014-2024, Regents of the University of California,
4  * Arizona Board of Regents,
5  * Colorado State University,
6  * University Pierre & Marie Curie, Sorbonne University,
7  * Washington University in St. Louis,
8  * Beijing Institute of Technology,
9  * The University of Memphis.
10  *
11  * This file is part of NFD (Named Data Networking Forwarding Daemon).
12  * See AUTHORS.md for complete list of NFD authors and contributors.
13  *
14  * NFD is free software: you can redistribute it and/or modify it under the terms
15  * of the GNU General Public License as published by the Free Software Foundation,
16  * either version 3 of the License, or (at your option) any later version.
17  *
18  * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
19  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
20  * PURPOSE. See the GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License along with
23  * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
24  */
25 
26 #include "command-definition.hpp"
27 #include "status-report.hpp"
28 
29 #include <ndn-cxx/net/face-uri.hpp>
30 #include <ndn-cxx/util/backports.hpp>
31 #include <ndn-cxx/util/logger.hpp>
32 
33 #include <boost/lexical_cast.hpp>
34 
35 namespace nfd::tools::nfdc {
36 
37 NDN_LOG_INIT(nfdc.CommandDefinition);
38 
39 std::ostream&
40 operator<<(std::ostream& os, ArgValueType vt)
41 {
42  switch (vt) {
43  case ArgValueType::NONE:
44  return os << "none";
45  case ArgValueType::ANY:
46  return os << "any";
48  return os << "boolean";
50  return os << "non-negative integer";
52  return os << "string";
54  return os << "ReportFormat";
55  case ArgValueType::NAME:
56  return os << "Name";
58  return os << "FaceUri";
60  return os << "FaceId or FaceUri";
62  return os << "FacePersistency";
64  return os << "RouteOrigin";
65  }
66  return os << static_cast<int>(vt);
67 }
68 
69 static std::string
71 {
72  switch (vt) {
73  case ArgValueType::NONE:
74  return "";
75  case ArgValueType::ANY:
76  return "args";
78  return "bool";
80  return "uint";
82  return "str";
84  return "fmt";
85  case ArgValueType::NAME:
86  return "name";
88  return "uri";
90  return "face";
92  return "persistency";
94  return "origin";
95  }
96  NDN_CXX_UNREACHABLE;
97 }
98 
99 CommandDefinition::CommandDefinition(std::string_view noun, std::string_view verb)
100  : m_noun(noun)
101  , m_verb(verb)
102 {
103 }
104 
106 
108 CommandDefinition::addArg(const std::string& name, ArgValueType valueType,
109  Required isRequired, Positional allowPositional,
110  const std::string& metavar)
111 {
112  bool isNew = m_args.emplace(name,
113  Arg{name, valueType, static_cast<bool>(isRequired),
114  metavar.empty() ? getMetavarFromType(valueType) : metavar}).second;
115  BOOST_VERIFY(isNew);
116 
117  if (static_cast<bool>(isRequired)) {
118  m_requiredArgs.insert(name);
119  }
120 
121  if (static_cast<bool>(allowPositional)) {
122  BOOST_ASSERT(valueType != ArgValueType::NONE);
123  m_positionalArgs.push_back(name);
124  }
125  else {
126  BOOST_ASSERT(valueType != ArgValueType::ANY);
127  }
128 
129  return *this;
130 }
131 
133 CommandDefinition::parse(const std::vector<std::string>& tokens, size_t start) const
134 {
135  CommandArguments ca;
136 
137  size_t positionalArgIndex = 0;
138  for (size_t i = start; i < tokens.size(); ++i) {
139  const std::string& token = tokens[i];
140 
141  // try to parse as named argument
142  auto namedArg = m_args.find(token);
143  if (namedArg != m_args.end() && namedArg->second.valueType != ArgValueType::ANY) {
144  NDN_LOG_TRACE(token << " is a named argument");
145  const Arg& arg = namedArg->second;
146  if (arg.valueType == ArgValueType::NONE) {
147  ca[arg.name] = true;
148  NDN_LOG_TRACE(token << " is a no-param argument");
149  }
150  else if (i + 1 >= tokens.size()) {
151  NDN_THROW(Error(arg.name + ": " + arg.metavar + " is missing"));
152  }
153  else {
154  const std::string& valueToken = tokens[++i];
155  NDN_LOG_TRACE(arg.name << " has value " << valueToken);
156  try {
157  ca[arg.name] = parseValue(arg.valueType, valueToken);
158  }
159  catch (const std::exception& e) {
160  NDN_LOG_TRACE(valueToken << " cannot be parsed as " << arg.valueType);
161  NDN_THROW_NESTED(Error(arg.name + ": cannot parse '" + valueToken + "' as " +
162  arg.metavar + " (" + e.what() + ")"));
163  }
164  NDN_LOG_TRACE(valueToken << " is parsed as " << arg.valueType);
165  }
166 
167  // disallow positional arguments after named argument
168  positionalArgIndex = m_positionalArgs.size();
169  continue;
170  }
171 
172  // try to parse as positional argument
173  for (; positionalArgIndex < m_positionalArgs.size(); ++positionalArgIndex) {
174  const Arg& arg = m_args.at(m_positionalArgs[positionalArgIndex]);
175 
176  if (arg.valueType == ArgValueType::ANY) {
177  std::vector<std::string> values;
178  std::copy(tokens.begin() + i, tokens.end(), std::back_inserter(values));
179  ca[arg.name] = values;
180  NDN_LOG_TRACE((tokens.size() - i) << " tokens are consumed for " << arg.name);
181  i = tokens.size();
182  break;
183  }
184 
185  try {
186  ca[arg.name] = parseValue(arg.valueType, token);
187  NDN_LOG_TRACE(token << " is parsed as value for " << arg.name);
188  break;
189  }
190  catch (const std::exception& e) {
191  if (arg.isRequired) { // the current token must be parsed as the value for arg
192  NDN_LOG_TRACE(token << " cannot be parsed as value for " << arg.name);
193  NDN_THROW_NESTED(Error("cannot parse '" + token + "' as an argument name or as " +
194  arg.metavar + " for " + arg.name + " (" + e.what() + ")"));
195  }
196  else {
197  // the current token may be a value for next positional argument
198  NDN_LOG_TRACE(token << " cannot be parsed as value for " << arg.name);
199  }
200  }
201  }
202 
203  if (positionalArgIndex >= m_positionalArgs.size()) {
204  // for loop has reached the end without finding a match,
205  // which means token is not accepted as a value for positional argument
206  NDN_THROW(Error("cannot parse '" + token + "' as an argument name"));
207  }
208 
209  // token is accepted; don't parse as the same positional argument again
210  ++positionalArgIndex;
211  }
212 
213  for (const auto& argName : m_requiredArgs) {
214  if (ca.count(argName) == 0) {
215  NDN_THROW(Error(argName + ": required argument is missing"));
216  }
217  }
218 
219  return ca;
220 }
221 
222 static bool
223 parseBoolean(const std::string& s)
224 {
225  if (s == "on" || s == "true" || s == "enabled" || s == "yes" || s == "1") {
226  return true;
227  }
228  if (s == "off" || s == "false" || s == "disabled" || s == "no" || s == "0") {
229  return false;
230  }
231  NDN_THROW(std::invalid_argument("unrecognized boolean value '" + s + "'"));
232 }
233 
234 static ReportFormat
235 parseReportFormat(const std::string& s)
236 {
237  if (s == "xml") {
238  return ReportFormat::XML;
239  }
240  if (s == "text") {
241  return ReportFormat::TEXT;
242  }
243  NDN_THROW(std::invalid_argument("unrecognized ReportFormat '" + s + "'"));
244 }
245 
246 static FacePersistency
247 parseFacePersistency(const std::string& s)
248 {
249  if (s == "persistent") {
250  return FacePersistency::FACE_PERSISTENCY_PERSISTENT;
251  }
252  if (s == "permanent") {
253  return FacePersistency::FACE_PERSISTENCY_PERMANENT;
254  }
255  NDN_THROW(std::invalid_argument("unrecognized FacePersistency '" + s + "'"));
256 }
257 
258 std::any
259 CommandDefinition::parseValue(ArgValueType valueType, const std::string& token)
260 {
261  switch (valueType) {
262  case ArgValueType::NONE:
263  case ArgValueType::ANY:
264  break;
265 
267  return parseBoolean(token);
268 
269  case ArgValueType::UNSIGNED: {
270  // boost::lexical_cast<uint64_t> will accept negative number
271  int64_t v = boost::lexical_cast<int64_t>(token);
272  if (v < 0) {
273  NDN_THROW(std::out_of_range("value '" + token + "' is negative"));
274  }
275  return static_cast<uint64_t>(v);
276  }
277 
279  return token;
280 
282  return parseReportFormat(token);
283 
284  case ArgValueType::NAME:
285  return Name(token);
286 
288  return ndn::FaceUri(token);
289 
291  try {
292  return boost::lexical_cast<uint64_t>(token);
293  }
294  catch (const boost::bad_lexical_cast&) {
295  return ndn::FaceUri(token);
296  }
297 
299  return parseFacePersistency(token);
300 
302  return boost::lexical_cast<RouteOrigin>(token);
303  }
304 
305  NDN_CXX_UNREACHABLE;
306 }
307 
308 } // namespace nfd::tools::nfdc
Contains named command arguments.
CommandArguments parse(const std::vector< std::string > &tokens, size_t start=0) const
Parse a command line.
CommandDefinition & addArg(const std::string &name, ArgValueType valueType, Required isRequired=Required::NO, Positional allowPositional=Positional::NO, const std::string &metavar="")
Declare an argument.
CommandDefinition(std::string_view noun, std::string_view verb)
std::ostream & operator<<(std::ostream &os, ArgValueType vt)
Required
Indicates whether an argument is required.
static FacePersistency parseFacePersistency(const std::string &s)
static std::string getMetavarFromType(ArgValueType vt)
static bool parseBoolean(const std::string &s)
static ReportFormat parseReportFormat(const std::string &s)
NDN_LOG_INIT(nfdc.CommandDefinition)
Positional
Indicates whether an argument can be specified as positional.
ArgValueType
Indicates argument value type.
@ FACE_ID_OR_URI
FaceId or FaceUri.
@ REPORT_FORMAT
Report format 'xml' or 'text'.
@ UNSIGNED
Non-negative integer.
@ NONE
Boolean argument without value.
@ FACE_PERSISTENCY
Face persistency 'persistent' or 'permanent'.