Monday, March 27, 2023

Create modern looking CLI tool with command line verbs

More and more CLI tools (command line interface) or simply console apps use unified approach for processing command line arguments. In this post I will write about one way of creating these apps using CommandLineParser. Like it is said on the project's github page CommandLineParser is

API for manipulating command line arguments and related tasks, such as defining switches, options and verb commands

Suppose that we need to create CLI tool for performing basic CRUD operations on some entities (e.g. users) in some data storage. Using syntax of CommandLineParser we may define 4 so called verbs for these operations:

  • list - list all users
  • details - read details of single user
  • save - insert/update single user
  • delete - delete single user

Verb is logical group of parameters. This is very convenient abstraction because in real life often different operations require different parameters and add them all into one single list of arguments is not convinient.

In our example list doesn't require additional parameters, details and delete operations may require e.g. id property of the user which should be affected by these operations and save may require all other fields (e.g. first name, last name, email, etc - all properties of the user which should be stored to the underlying storage).

When we defined verbs and their parameters we need to create classes for each verb like this (before that we need to add CommandLineParser nuget package to the project):

[Verb("list", HelpText = "List all users from storage")]
public class ListOptions
{
}

[Verb("details", HelpText = "Read details of specific user from storage")]
public class DetailsOptions
{
    [Option("id", Required = true, HelpText = "User id")]
    public string Id { get; set; }
}

[Verb("save", HelpText = "Save user to storage")]
public class SaveOptions
{
    [Option("firstName", Required = true, HelpText = "User first name")]
    public string FirstName { get; set; }

    [Option("lastName", Required = true, HelpText = "User last name")]
    public string LastName { get; set; }

    [Option("email", Required = true, HelpText = "User email")]
    public string Email { get; set; }
}

[Verb("delete", HelpText = "Delete specific user from storage")]
public class DeleteOptions
{
    [Option("id", Required = true, HelpText = "User id")]
    public string Id { get; set; }
}

With these verbs command line processing will look like that:

Parser.Default.ParseArguments<ListOptions, DetailsOptions, SaveOptions, DeleteOptions>(args)
    .WithParsed<ListOptions>(opts => worker.DoWork(opts))
    .WithParsed<DetailsOptions>(opts => worker.DoWork(opts))
    .WithParsed<SaveOptions>(opts => worker.DoWork(opts))
    .WithParsed<DeleteOptions>(opts => worker.DoWork(opts));

In addition to that we need to define 4 methods in the Worker class which will do actual work for each operation:

public class Worker
{
    public void DoWork(ListOptions opt)
    {
    	//...
    }
    
    public void DoWork(DetailsOptions opt)
    {
    	//...
    }
    
    public void DoWork(SaveOptions opt)
    {
    	//...
    }
    
    public void DoWork(DeleteOptions opt)
    {
    	//...
    }
}

That's it. Now lets run our CLI tool without command line parameters - it tell that no verb is specified and will list all available operations with their descriptions:

Based on this list it is clear that we may read all users like this:

CLITool.exe list

If we will run details command without providing id

CLITool.exe details

it will show the following error:

telling us that correct syntax is

CLITool.exe details --id=1

or

CLITool.exe details --id 1

Also we have builtin support of help and version commands. E.g. this is how we may get more information about delete command:

CLITool.exe help delete

which will show us syntax of delete command:

Hope that this information will be useful and we will have more unified CLI tools in future with help of CommandLineParser.

No comments:

Post a Comment