Tutorial MUD, part 3: Argument parsing

In this third part of Tutorial MUD, we will start parsing the command line options. We will continue working on the code done in part 1 and part 2, so we will have an almost empty main function and start from that. The code in part 2 can be found in github.

We will be putting the command-line parsing in a separate namespace, to not clutter up the global namespace. Since command-line argument can be seen as configuring the runtime  properties of the program, the new namespace will be called config. Lets start by creating two new files, config.h and config.cpp, for this namespace.

The header file:

[codesyntax lang=”cpp”]

namespace config
{
	void parse_arguments(int argc, char *argv[]);
}

[/codesyntax]

The source file:

[codesyntax lang=”cpp”]

#include "tm.h"
#include "config.h"

#include <unistd.h>

[/codesyntax]

Now lets create the function that does the argument parsing. It will not be as hard as you might think, because there is already an existing function in the system that does most of the hard work for us: getopt. The getopt function do of course need the argc and argv arguments from main, so our function needs these as arguments as well.

[codesyntax lang=”cpp”]

void config::parse_arguments(int argc, char *argv[])
{
}

[/codesyntax]

Before we continue, it might be a good idea to figure out exactly what arguments we expect the program to handle. Things like the network port (the “port number” that players connect to) and if to put the logging output into a file are good candidates. We will start with the logging output, and add new parameters when we come to those in later parts. Two other arguments that are common is one to print a simple help screen describing the options, and one to display the program version number.

Like said above, we will be using the getopt function to do the actual heavy lifting for us. If you check the documentation you will see that it takes three arguments: argc and argv as provided to main, and an option string that describes the arguments. It returns an integer which will either be the same as the option characters passed in the option string, the special characters '?'or ':', or -1 if all options have been parsed. As you notice, we have to call getopt in a loop until it returns -1.

The third argument, the option string, is very simple: It contains a list of characters that represents the options. If the options requires an argument, then follow that character with a colon ':'. Lets start with a version that checks for the help and version options:

[codesyntax lang=”cpp”]

void config::parse_arguments(int argc, char *argv[])
{
	int c;  // The returned option from getopt

	while ((c = getopt(argc, argv, "hv")) != -1)
	{
		switch (c)
		{
		case 'h':
			std::cout << "Print some help\n";
			std::exit(0);
			break;
		case 'v':
			std::cout << "Print version number\n";
			std::exit(0);
			break;

		case '?':
			std::cout << "Print some help\n";
			std::exit(1);
			break;

		default:
			std::cerr << "Error: Unknown value returned by getopt: " << c << '\n';
			std::exit(1);
			break;
		}
	}
}

[/codesyntax]

As you can see, this is pretty simple stuff. If we now add it to main we will be able to add the arguments -v or -h when running the program:

[codesyntax lang=”cpp”]

	parse_arguments(argc, argv);

[/codesyntax]

If an unknown argument is given, getopt prints an error and returns the character '?'. It is customary to print out the help when it happens.

If the version or help options are given on the command line, or if there is an error, we call std::exit() to exit the program. This is also customary.

Printing of help and version

When asked to, we should of course print a help-full message instead of what we do now. Since this will be printed from two places it’s a good candidate to put in a function. So the function name is not exported to other source modules, we will put it in an anonymous namespace:

[codesyntax lang=”cpp”]

namespace
{
	void print_help(const std::string &argv0)
	{
		std::cout << "Usage: " << argv0 << " [-l <filename>] [-h] [-v]\n";
		std::cout << "Where:\n";
		std::cout << "    -l <filename>    Log to named file\n";
		std::cout << "    -h               Print this help message and exit\n";
		std::cout << "    -v               Print version number and exit\n";
	}
}

[/codesyntax]

Of course, we need to call it too:

[codesyntax lang=”cpp”]

	case 'h':
		print_help(argv[0]);
		std::exit(0);
		break;

	case '?':
		print_help(argv[0]);
		std::exit(0);
		break;

[/codesyntax]

The first entry in argv, argv[0], contains the program names as given on the command line. The print_help function also contains help for a new option, one to set the log file.

Log file

We will now start with an option to get a file-name to use for logging, if the person running the server don’t want it on the standard output. This should be stored in the config namespace as a string variable:

[codesyntax lang=”cpp”]

	std::string log_file_name;

[/codesyntax]

After parsing the arguments, the main function can check this parameters to know how to initialize the logging system we made in part 2.

[codesyntax lang=”cpp”]

	if (config::log_file_name.empty())
		logger::init();  // Use std::clog
	else
		logger::init(config::log_file_name);  // Use the named file

[/codesyntax]

To add this option, we need to add it to the getopt call, and of course check for it in our switch-statement:

[codesyntax lang=”cpp”]

	while ((c = getopt(argc, argv, "l:hv")) != -1)
	{
		switch (c)
		{
		case 'l':
			log_file_name = optarg;
			break;

[/codesyntax]

We have now added 'l' to the option string, but it also contains a colon ':'. The colon tells getopt that the previous option ('l' in our case) needs an argument. This argument will be put in the global optarg variable.

This concludes part three of this tutorial. It can as usual be found on github under the part-3 tag.

Join the Conversation

1 Comment

Leave a comment