The arguments via command line leveraged the UNIX commands making them into high power tools.
Today we are going to meet argparse
a library written in Modern C++. This tool not only helps you to create arguments in a professional way, but also to create help
in a practical way.
Dependencies:
Run the commands in order:
git clone https://github.com/p-ranav/argparse
cd argparse
mkdir build
cd build
cmake -DARGPARSE_INSTALL=on ..
sudo make install
The files will be installed in:
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/lib64/cmake/argparse/argparseConfig.cmake
-- Installing: /usr/local/include/argparse/argparse.hpp
-- Installing: /usr/local/lib64/cmake/argparse/argparseConfig-version.cmake
-- Installing: /usr/local/lib64/pkgconfig/argparse.pc
To test a basic file would be the example provided in the documentation
#include <argparse/argparse.hpp> // header
int main(int argc, char *argv[]) { // important
argparse::ArgumentParser program("program_name"); // change to your command name, eg "square"
program.add_argument("square")
.help("display the square of a given integer") // automatically add to help
.scan<'i', int>(); // check for parameters that are digits only
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) { // note that since it's just checking if the arguments were passed, so it's 'runtime_error'
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
auto input = program.get<int>("square"); // stores the parameter to a variable so we can manipulate it more easily
std::cout << (input * input) << std::endl; // execute the operation
return 0;
}
Let’s see more options by creating a command from scratch.
Let’s create a command similar to the UNIX cat
command, but let’s call it: mycat
.
#include <argparse/argparse.hpp>
ArgumentParser
and enter the name of our command:Remembering that your
main()
function needs to receive parameters, for example:int main (int argc, char **argv)
.
argparse::ArgumentParser app("mycat");
help
Instruct the command to receive at least 1 file as a parameter. And already add this to --help
.
app.add_argument("files")
.help("Inform the file(s) you want to see the content.");
try {
app.parse_args(argc, argv);
}catch (const std::runtime_error &err){ // runtime_error
std::cerr << err.what() << '\n';
std::cerr << app;
std::exit(1);
}
try{
auto files = app.get<std::vector<std::string>>("files");
}catch(const std::logic_error &err){ // logic_error
std::cerr << err.what() << '\n';
std::cerr << app;
std::exit(1);
}
auto files = app.get<std::vector<std::string>>("files");
for(auto &file : files){
std::cout << "FILE: " << file << '\n';
}
To compile, you don’t need any specific flag, just compile and run:
g++ mycat.cpp -o mycat
Before running let’s use this file as an example
file.txt
Of course life is good
And joy, the only unspeakable emotion
Of course I think you're beautiful
In you I bless the love of simple things
Of course I love you
And I have everything to be happy
But I happen to be sad...
If we just run the program/command without informing any files, it already shows the type of error in the first line and then the help already with the customized data for our command:
$ ./mycat
files: 1 argument(s) expected. 0 provided.
Usage: mycat [--help] [--version] files
Positional arguments:
files Inform the file(s) you want to see the content.
Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
If we pass the file, it already lists:
./mycat file.txt
As we want to display the contents of the file instead of the list, let’s just create a function named mycat()
and of type void receiving a string as a parameter
:
#include <fstream>
void mycat(const std::string &name){
std::string line{};
std::ifstream file(name);
while(std::getline(file, line)){
std::cout << line << '\n';
}
}
And in our loop
we will replace std::cout
with the function passing the name of our file:
for(auto &file : files){
mycat(file);
}
This is a basic example, but it would be right to include the
<filesystem>
and create anothertry catch
to check if thefiles
(strings) are of type file.
Now if we recompile and run it, it will already display the contents of the file:
g++ mycat.cpp -o mycat
./mycat file.txt
The cat
command has a -n
or --number
parameter which displays line numbers. Let’s implement this parameter to our mycat
.
First let’s add a new argument:
There are several union functions that we can add, in this case we will use two:
.default_value(false)
- if you want a default value;.implicit_value(true);
- does not require specific value;app.add_argument("-n", "--number")
.help("Display the number of lines")
.default_value(false)
.implicit_value(true);
leave it like that
void mycat(const std::string &name, bool check){
std::string line{};
int number {1};
std::ifstream file(name);
while(std::getline(file, line)){
if(check){
std::cout << number << "." << line << '\n';
++number;
}else{
std::cout << line << '\n';
}
}
}
And our check will look like this:
bool check{false};
if( app["-n"] == true ){
check = true;
}
auto files = app.get<std::vector<std::string>>("files");
for(auto &file : files){
mycat(file, check);
}
Recompile and test in several ways:
g++ mycat.cpp -o mycat
./mycat file.txt
./mycat --number file.txt
./mycat -n file.txt
./mycat file.txt --number
.mycat file.txt -n
app("mycat", "2.3.0");
argparse::ArgumentParser app("mycat", "2.3.0", argparse::default_arguments::none);
app.add_argument("-h", "--help")
.help("Show this help");
app.add_argument("-v", "--version")
.help("Show the version");
app.add_description("A minimalist alternative to the 'cat' command.");
app.add_argument("files")
.help("Enter the file(s) you want to see the content.")
.remaining();
And use:
/.mycat file1.txt file2.txt file3.txt
/.mycat -n file1.txt file2.txt file3.txt
And among several possibilities that can be found in the documentation.