NCBI C++ ToolKit
multi_command.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: multi_command.cpp 90001 2020-05-04 12:53:02Z ivanov $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors: Denis Vakatov, Andrei Gourianov, David McElhany
27  *
28  * File Description:
29  *
30  * --- General ---
31  *
32  * This program demonstrates how to set up and process command-line arguments
33  * for programs that support "command-based" command lines instead of, or in
34  * addition to, a "command-less" command line.
35  * - A "command-based" command line begins with a "command" (a case-sensitive
36  * keyword), typically followed by other arguments. See the description of
37  * the "post" command below.
38  * - A "command-less" command line doesn't contain such "commands".
39  * - Programs that support command-based command lines can support any number
40  * of commands (each with its own set of supported arguments), and may
41  * optionally support a command-less command line as well.
42  *
43  * To demonstrate setting up and processing command-based usage forms in the
44  * most clear and concise way, this program does not demonstrate the basics of
45  * command-less command-line argument processing. If you'd like to see how to
46  * set up optional and mandatory flags, keys, positional arguments, etc.,
47  * please see basic_sample.cpp.
48  *
49  * It is assumed that the reader is familiar with the NCBI application
50  * framework class CNcbiApplication and with unique_ptr.
51  *
52  * --- Program Invocation and Supported Features ---
53  *
54  * Here are the command-based usage forms this program supports:
55  * multi_command list
56  * multi_command create <queue>
57  * multi_command post <queue> [-imp importance] <message>
58  * multi_command query [queue]
59  *
60  * Here are some examples of possible command-based invocations:
61  * multi_command create myqueue-101
62  * multi_command list
63  * multi_command post myqueue-101 -imp URGENT "Stop all operations!"
64  * multi_command query
65  *
66  * With the "list" command, no additional arguments are necessary.
67  * With "create", the "queue" positional argument is required.
68  * With "query", the "queue" positional argument is optional.
69  *
70  * The "post" command illustrates a new feature introduced with command-based
71  * command lines - opening arguments. Opening arguments are essentially
72  * identical to mandatory positional arguments except that opening arguments
73  * must precede optional arguments whereas mandatory positional arguments
74  * must follow them. Thus, opening arguments allow usage forms such as the
75  * "post" command in this program, which has an optional argument between
76  * mandatory arguments.
77  *
78  * In addition to the commands listed above, this program also supports this
79  * this command-less usage form:
80  * multi_command [-v] <script_file>
81  *
82  * Here is an example invocation using a command-less command line:
83  * multi_command -v routine_maint.cmds
84  *
85  * --- Required Steps to Process Command-Based Command-Lines ---
86  *
87  * At a high level, setting up a program to support a command-less command-line
88  * requires creating a CArgDescriptions object, adding argument descriptions
89  * to it, and passing it to SetupArgDescriptions().
90  *
91  * Setting up a program to support command-based command lines is similar, but
92  * requires a CCommandArgDescriptions instead. The CCommandArgDescriptions
93  * class is derived from CArgDescriptions, so all the same functionality is
94  * available; however, the AddCommand() method of CCommandArgDescriptions
95  * allows you to create multiple CArgDescriptions objects (one for each
96  * command) in addition to the overall program description. Other command-
97  * specific features are also provided, such as command grouping and
98  * opening arguments.
99  *
100  * Programs that support command-based command lines must execute these steps:
101  * 1. Create a command descriptions object (class CCommandArgDescriptions)
102  * for the overall program description.
103  * 2. Create argument descriptions objects (class CArgDescriptions) for each
104  * command.
105  * 3. Add the actual argument descriptions to the argument descriptions
106  * objects using methods such as AddOpening(), AddPositional(), etc.
107  * 4. Add each argument descriptions object to the overall command
108  * descriptions object.
109  * 5. Determine which command was specified on the command line.
110  * 6. Process the appropriate arguments for the given command.
111  *
112  * --- Program Organization ---
113  *
114  * The first four required steps above could be done in one long sequence of
115  * statements in the application's Init() function. However, this could become
116  * unwieldy for programs with many commands. Factoring those steps into
117  * dedicated "Init" functions for each command keeps the application's Init()
118  * function readable.
119  *
120  * Similarly, the sixth required step could be implemented as an "if-else-if"
121  * type construct in the application's Run() function, with a branch for each
122  * command. However, a cleaner approach is to create a dedicated "Run"
123  * function for each command.
124  *
125  * This program is arranged in essentially three sections (four if you include
126  * this rather long introductory comment):
127  * 1. Command-specific "Init" and "Run" functions. This section comes first
128  * because pointers to these functions are used in the next section. You
129  * may want to read this section last.
130  * 2. Constructs for simplifying the program - the command "metadata".
131  * 3. The application's class definition.
132  */
133 
134 #include <ncbi_pch.hpp>
135 #include <corelib/ncbiapp.hpp>
136 #include <corelib/ncbiargs.hpp>
137 
138 #include <common/test_assert.h> /* This header must go last, if used at all */
139 
141 
142 
143 /////////////////////////////////////////////////////////////////////////////
144 // "Init" and "Run" functions for each command
145 //
146 // The "Init" functions add the command-specific argument descriptions.
147 // These descriptions are used when parsing the command line and when
148 // generating the USAGE statement.
149 //
150 // The "Run" functions use the arguments set up by the corresponding "Init"
151 // functions to execute the command-specific functionality.
152 
153 
154 // Note: The "list" command doesn't have any additional arguments, so it
155 // doesn't have an "Init" function.
156 
157 static int s_RunList(const CArgs& args, ostream& os)
158 {
159  // The "list" command might print a list of queue names.
160  os << "Here is the list of queues: ..." << endl;
161  return 0;
162 }
163 
164 
165 static void s_InitCreate(CArgDescriptions& arg_desc)
166 {
167  // The "create" command requires the "queue" argument. Here it is being
168  // added as an opening argument - a mandatory argument preceding any
169  // other arguments.
170  arg_desc.AddOpening("queue", "Queue name", CArgDescriptions::eString);
171 
172  // Note that because the "queue" argument isn't followed by any optional
173  // arguments, it could just as well have been added as a positional
174  // argument:
175  //arg_desc.AddPositional("queue", "Queue name", CArgDescriptions::eString);
176 }
177 
178 static int s_RunCreate(const CArgs & args, ostream& os)
179 {
180  // The "create" command might create a queue with a given name.
181  os << "Creating queue '" << args["queue"].AsString() << "'." << endl;
182  return 0;
183 }
184 
185 
186 static void s_InitPost(CArgDescriptions& arg_desc)
187 {
188  // The "post" command requires the "queue" opening argument.
189  // AddOpening must be used in this case because the queue argument precedes
190  // a non-positional argument.
191  arg_desc.AddOpening("queue", "Queue name", CArgDescriptions::eString);
192 
193  // The "post" command allows the "imp" key and requires the "message"
194  // positional argument.
195  arg_desc.AddOptionalKey("imp", "Importance", "The message importance",
197  arg_desc.AddPositional("message", "The message to post",
199 }
200 
201 static int s_RunPost(const CArgs& args, ostream& os)
202 {
203  // The "post" command might send a message to a queue, with an optional
204  // importance value.
205  os << "Sending message '" << args["message"].AsString()
206  << "' to queue '" << args["queue"].AsString() << "'";
207  if ( args["imp"].HasValue() ) {
208  os << " with importance '" << args["imp"].AsString() << "'." << endl;
209  } else {
210  os << " with default importance." << endl;
211  }
212  return 0;
213 }
214 
215 
216 static void s_InitQuery(CArgDescriptions& arg_desc)
217 {
218  // The "query" command allows the "queue" argument. Because it is optional,
219  // it must be a positional argument, not an opening argument.
220  arg_desc.AddOptionalPositional("queue", "Queue name",
222 }
223 
224 static int s_RunQuery(const CArgs& args, ostream& os)
225 {
226  // The "query" command might print the messages for a given queue, or for
227  // all queues if none was specified.
228  if ( args["queue"].HasValue() ) {
229  os << "Messages for queue '" << args["queue"].AsString() << "': ..."
230  << endl;
231  } else {
232  os << "Messages for all queues: ..." << endl;
233  }
234  return 0;
235 }
236 
237 
238 // The following "Init" and "Run" functions are for the command-less usage
239 // form of the program, i.e. multi_command [-v] <script_file>
240 
241 static void s_CommandlessInit(CArgDescriptions& arg_desc)
242 {
243  // Example arguments for a command-less usage form:
244  arg_desc.AddFlag("v", "Verbose");
245  arg_desc.AddPositional("script_file", "A file containing commands",
247 }
248 
249 static int s_CommandlessRun(const CArgs& args, ostream& os)
250 {
251  // The command-less usage form would do something with the arguments.
252  os << "Executing script file '" << args["script_file"].AsString() << "'." << endl;
253  if ( args["v"].AsBoolean() ) {
254  os << " Blah, blah, blah..." << endl;
255  }
256  return 0;
257 }
258 
259 
260 /////////////////////////////////////////////////////////////////////////////
261 // Convenience constructs
262 
263 // Function pointer types for the "Init" and "Run" functions.
264 typedef void (*FInit)(CArgDescriptions & arg_desc);
265 typedef int (*FRun)(const CArgs & args, ostream & os);
266 
267 // A structure to collect all the metadata for a command.
268 struct SCmd {
269  string m_Name; // the command's name - really, the command itself
270  string m_Desc; // its description
271  string m_Alias; // an alias
272  FInit m_Init; // the command's "Init" function
273  FRun m_Run; // the command's "Run" function
274 };
275 
276 // Specify the metadata and function pointers for all commands.
277 //
278 // The following array of structures collects the basic information about all
279 // the commands used in this program in one place. Using an array of structures
280 // like this is not a requirement for processing command-based arguments, but
281 // it greatly simplifies both the specification of the metadata and the
282 // application's Init() method. Alternatively, you could repeat the required
283 // steps - currently in a for loop in Init() - for each command, supplying the
284 // metadata as needed in each repeated group of statements.
285 
286 static SCmd s_Cmds[] = {
287  //name description alias "Init" function "Run" function
288  { "list", "list entries", "lst", NULL, s_RunList },
289  { "create", "create an entry", "crt", s_InitCreate, s_RunCreate },
290  { "post", "post an entry", "pst", s_InitPost, s_RunPost },
291  { "query", "select entries", "qry", s_InitQuery, s_RunQuery }
292 };
293 static size_t s_NumCmds = sizeof(s_Cmds) / sizeof(s_Cmds[0]);
294 
295 
296 /////////////////////////////////////////////////////////////////////////////
297 // CMultiCommandApplication::
298 
300 {
301 private:
302  virtual void Init(void);
303  virtual int Run(void);
304 };
305 
306 
307 // Set up the argument descriptions for all commands.
309 {
310  // Create the program's overall command-line argument descriptions class.
311  // This will be a container for both the command-less and command-based
312  // usage forms.
313  // The ECommandPresence parameter controls whether or not the user must
314  // enter a command-based command line. Use eCommandOptional only when you
315  // are setting up both command-less and command-based command lines.
316  unique_ptr<CCommandArgDescriptions> cmd_desc(
317  new CCommandArgDescriptions(true, 0,
319 
320  // Specify the overall program USAGE context.
321  cmd_desc->SetUsageContext(GetArguments().GetProgramBasename(),
322  "Demo program for command-based command-line "
323  "argument processing.", false);
324 
325  // Set up the argument descriptions for the command-less command line.
326  s_CommandlessInit(*cmd_desc);
327 
328  // The following loop sets up the separate commands.
329  for (size_t idx = 0; idx < s_NumCmds; ++idx) {
330 
331  // Create a container for this command's arguments. Argument
332  // descriptions are allocated on the heap.
333  unique_ptr<CArgDescriptions> arg_desc(new CArgDescriptions);
334 
335  // Describe this command (the name parameter is not used for commands).
336  // This description will be shown in the USAGE statement.
337  arg_desc->SetUsageContext("", s_Cmds[idx].m_Desc);
338 
339  // Set up this command's argument descriptions (if it has any) using
340  // its "Init" function.
341  if ( s_Cmds[idx].m_Init != NULL ) {
342  s_Cmds[idx].m_Init(*arg_desc);
343  }
344 
345  // OPTIONAL: Add this command to a group of commands. Command groups
346  // serve the sole purpose of organizing the commands in the USAGE
347  // statement.
348  // if ( this_command_should_be_grouped ) {
349  // cmd_desc->SetCurrentCommandGroup(the_group_name);
350  // }
351 
352  // Add this command (and its argument descriptions) to the program's
353  // overall argument descriptions object, 'cmd_desc'. Ownership is
354  // transfered to 'cmd_desc' by calling the unique_ptr's release() method
355  // and passing that to 'cmd_desc' via AddCommand().
356  cmd_desc->AddCommand(s_Cmds[idx].m_Name, arg_desc.release(),
357  s_Cmds[idx].m_Alias);
358  }
359 
360  // Finalize the setup for all of the program's argument descriptions
361  // (also transferring ownership of the argument descriptions).
362  //
363  // Note: This is also where the supplied command line is checked, and if
364  // it doesn't match a valid usage form, an exception will be thrown.
365  SetupArgDescriptions(cmd_desc.release());
366 }
367 
368 
369 // Process the arguments obtained from the command line.
371 {
372  // Get the specified command (if one is supplied).
373  string command(GetArgs().GetCommand());
374 
375  // Check for the command-less usage form.
376  if ( command == "" ) {
377  return s_CommandlessRun(GetArgs(), cout);
378  }
379 
380  // A command-based usage form was used, so call the command's "Run"
381  // function.
382  for (size_t idx = 0; idx < s_NumCmds; ++idx) {
383  if ( command == s_Cmds[idx].m_Name ) {
384  _ASSERT(s_Cmds[idx].m_Run != NULL); // empty "Run" is meaningless
385  return s_Cmds[idx].m_Run(GetArgs(), cout);
386  }
387  }
388 
389  // Should never get here. If the command line is not valid, an exception
390  // will be thrown in Init(). If it is valid, then it will either result in
391  // s_CommandlessRun() or one of the command's "Run" functions being called.
392  _ASSERT(false);
393  return 1;
394 }
395 
396 
397 int NcbiSys_main(int argc, ncbi::TXChar* argv[])
398 {
399  return CMultiCommandApplication().AppMain(argc, argv);
400 }
CArgDescriptions –.
Definition: ncbiargs.hpp:541
CArgs –.
Definition: ncbiargs.hpp:379
CCommandArgDescriptions –.
Definition: ncbiargs.hpp:1381
virtual int Run(void)
Run the application.
virtual void Init(void)
Initialize the application.
virtual const CArgs & GetArgs(void) const
Get parsed command line arguments.
Definition: ncbiapp.cpp:305
int AppMain(int argc, const char *const *argv, const char *const *envp=0, EAppDiagStream diag=eDS_Default, const char *conf=NcbiEmptyCStr, const string &name=NcbiEmptyString)
Main function (entry point) for the NCBI application.
Definition: ncbiapp.cpp:819
virtual void SetupArgDescriptions(CArgDescriptions *arg_desc)
Setup the command line argument descriptions.
Definition: ncbiapp.cpp:1195
const CNcbiArguments & GetArguments(void) const
Get the application's cached unprocessed command-line arguments.
void AddFlag(const string &name, const string &comment, CBoolEnum< EFlagValue > set_value=eFlagHasValueIfSet, TFlags flags=0)
Add description for flag argument.
Definition: ncbiargs.cpp:2459
void AddPositional(const string &name, const string &comment, EType type, TFlags flags=0)
Add description for mandatory positional argument.
Definition: ncbiargs.cpp:2471
void AddOptionalKey(const string &name, const string &synopsis, const string &comment, EType type, TFlags flags=0)
Add description for optional key without default value.
Definition: ncbiargs.cpp:2427
void AddOptionalPositional(const string &name, const string &comment, EType type, TFlags flags=0)
Add description for optional positional argument without default value.
Definition: ncbiargs.cpp:2497
void AddOpening(const string &name, const string &comment, EType type, TFlags flags=0)
Add description of mandatory opening positional argument.
Definition: ncbiargs.cpp:2484
@ eCommandOptional
Command is not necessary.
Definition: ncbiargs.hpp:1386
@ eString
An arbitrary string.
Definition: ncbiargs.hpp:589
#define NULL
Definition: ncbistd.hpp:225
char TXChar
Definition: ncbistr.hpp:172
unsigned int
A callback function used to compare two keys in a database.
Definition: types.hpp:1210
static size_t s_NumCmds
int(* FRun)(const CArgs &args, ostream &os)
static SCmd s_Cmds[]
static void s_CommandlessInit(CArgDescriptions &arg_desc)
static int s_RunQuery(const CArgs &args, ostream &os)
int NcbiSys_main(int argc, ncbi::TXChar *argv[])
static void s_InitQuery(CArgDescriptions &arg_desc)
static void s_InitCreate(CArgDescriptions &arg_desc)
static int s_RunPost(const CArgs &args, ostream &os)
void(* FInit)(CArgDescriptions &arg_desc)
static int s_RunCreate(const CArgs &args, ostream &os)
static void s_InitPost(CArgDescriptions &arg_desc)
USING_NCBI_SCOPE
static int s_CommandlessRun(const CArgs &args, ostream &os)
static int s_RunList(const CArgs &args, ostream &os)
Defines the CNcbiApplication and CAppException classes for creating NCBI applications.
Defines command line argument related classes.
const char * command
string m_Desc
FRun m_Run
string m_Name
string m_Alias
FInit m_Init
#define _ASSERT
Modified on Sun May 05 05:21:27 2024 by modify_doxy.py rev. 669887