CommandLineParser
A mature command-line argument parser for C#. Features an attribute-based declarative approach.
Framework
CommandLineParser
Overview
CommandLineParser is a mature command-line argument parser for C#. It features an attribute-based declarative approach and maintains stable popularity over the years, being trusted in many .NET projects. With rich functionality and an intuitive API, it can handle everything from simple CLI tools to complex applications.
Details
CommandLineParser is an open-source library available for .NET Framework, .NET Core, and .NET 5+. It defines option classes using attributes and automatically parses command-line arguments to map them to objects. It provides rich customization options and comprehensive error handling to support professional CLI application development.
Key Features
- Attribute-Based Design: Declarative option definition using attributes
- Automatic Mapping: Automatically maps command-line arguments to object properties
- Rich Type Support: Supports primitive types, enums, collections, and custom types
- Validation: Built-in and custom validation functionality
- Automatic Help Generation: Automatically generates beautifully organized help text
- Internationalization: Supports localization features
- Mutually Exclusive Options: Mutual exclusion constraints between options
- Dynamic Options: Ability to add options dynamically at runtime
Pros and Cons
Pros
- Declarative Approach: Intuitive option definition using attributes
- Type Safety: Safe argument processing through strong typing
- Rich Features: Validation, mutual exclusion, customization, etc.
- Mature Library: Long track record and stability
- Excellent Documentation: Comprehensive official documentation and samples
- Active Community: Continuous development and support
- NuGet Support: Easy package management
Cons
- Learning Curve: Understanding the attribute system is required
- Reflection Usage: Performance overhead from runtime reflection
- Complex Configuration: Complexity of configuration when using advanced features
- Dependencies: Dependency on external libraries
Key Links
Usage Examples
Basic Usage
using CommandLine;
using System;
public class Options
{
[Option('v', "verbose", Required = false, HelpText = "Enable verbose output")]
public bool Verbose { get; set; }
[Option('f', "file", Required = true, HelpText = "File name to process")]
public string FileName { get; set; }
[Option('c', "count", Required = false, Default = 1, HelpText = "Number of times to process")]
public int Count { get; set; }
}
class Program
{
static void Main(string[] args)
{
Parser.Default.ParseArguments<Options>(args)
.WithParsed<Options>(opts => RunOptions(opts))
.WithNotParsed<Options>((errs) => HandleParseError(errs));
}
static void RunOptions(Options opts)
{
Console.WriteLine($"File name: {opts.FileName}");
Console.WriteLine($"Process count: {opts.Count}");
if (opts.Verbose)
{
Console.WriteLine("Verbose mode enabled");
}
// Implement main processing here
for (int i = 0; i < opts.Count; i++)
{
Console.WriteLine($"Processing ({i + 1}/{opts.Count}): {opts.FileName}");
}
}
static void HandleParseError(IEnumerable<Error> errs)
{
foreach (var error in errs)
{
Console.WriteLine($"Error: {error}");
}
}
}
Multiple Subcommands
using CommandLine;
using System;
using System.Collections.Generic;
using System.Linq;
[Verb("add", HelpText = "Add files")]
public class AddOptions
{
[Option('f', "files", Required = true, HelpText = "Files to add")]
public IEnumerable<string> Files { get; set; }
[Option('r', "recursive", Required = false, HelpText = "Add recursively")]
public bool Recursive { get; set; }
[Option('v', "verbose", Required = false, HelpText = "Verbose output")]
public bool Verbose { get; set; }
}
[Verb("remove", HelpText = "Remove files")]
public class RemoveOptions
{
[Option('f', "files", Required = true, HelpText = "Files to remove")]
public IEnumerable<string> Files { get; set; }
[Option("force", Required = false, HelpText = "Force removal")]
public bool Force { get; set; }
[Option('v', "verbose", Required = false, HelpText = "Verbose output")]
public bool Verbose { get; set; }
}
[Verb("list", HelpText = "List files")]
public class ListOptions
{
[Option('p', "path", Required = false, Default = ".", HelpText = "Search path")]
public string Path { get; set; }
[Option('a', "all", Required = false, HelpText = "Show hidden files")]
public bool ShowHidden { get; set; }
[Option('l', "long", Required = false, HelpText = "Long format display")]
public bool LongFormat { get; set; }
}
class Program
{
static int Main(string[] args)
{
return Parser.Default.ParseArguments<AddOptions, RemoveOptions, ListOptions>(args)
.MapResult(
(AddOptions opts) => RunAddCommand(opts),
(RemoveOptions opts) => RunRemoveCommand(opts),
(ListOptions opts) => RunListCommand(opts),
errs => 1);
}
static int RunAddCommand(AddOptions options)
{
Console.WriteLine("Running add command...");
foreach (var file in options.Files)
{
if (options.Verbose)
{
Console.WriteLine($"Adding: {file}");
}
}
if (options.Recursive)
{
Console.WriteLine("Recursive processing enabled");
}
return 0;
}
static int RunRemoveCommand(RemoveOptions options)
{
Console.WriteLine("Running remove command...");
foreach (var file in options.Files)
{
if (options.Verbose)
{
Console.WriteLine($"Removing: {file}");
}
}
if (options.Force)
{
Console.WriteLine("Force removal mode");
}
return 0;
}
static int RunListCommand(ListOptions options)
{
Console.WriteLine($"Listing files: {options.Path}");
if (options.ShowHidden)
{
Console.WriteLine("Showing hidden files");
}
if (options.LongFormat)
{
Console.WriteLine("Long format display");
}
return 0;
}
}
Advanced Attributes and Validation
using CommandLine;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
public class AdvancedOptions
{
[Option('i', "input", Required = true, HelpText = "Input file path")]
[FileExists]
public string InputFile { get; set; }
[Option('o', "output", Required = false, HelpText = "Output file path")]
public string OutputFile { get; set; }
[Option('p', "port", Required = false, Default = 8080, HelpText = "Port number (1024-65535)")]
[Range(1024, 65535)]
public int Port { get; set; }
[Option('e', "email", Required = false, HelpText = "Email address")]
[EmailAddress]
public string Email { get; set; }
[Option('t', "tags", Required = false, HelpText = "Tag list (comma-separated)")]
public IEnumerable<string> Tags { get; set; }
[Option("format", Required = false, Default = OutputFormat.Json, HelpText = "Output format")]
public OutputFormat Format { get; set; }
[Option("config", Required = false, HelpText = "Configuration file path")]
public string ConfigFile { get; set; }
// Mutually exclusive options
[Option('q', "quiet", Required = false, HelpText = "Quiet mode", Group = "verbosity")]
public bool Quiet { get; set; }
[Option('v', "verbose", Required = false, HelpText = "Verbose mode", Group = "verbosity")]
public bool Verbose { get; set; }
}
public enum OutputFormat
{
Json,
Xml,
Csv,
Text
}
// Custom validation attributes
public class FileExistsAttribute : Attribute
{
// Validation logic implemented separately
}
public class RangeAttribute : Attribute
{
public int Min { get; }
public int Max { get; }
public RangeAttribute(int min, int max)
{
Min = min;
Max = max;
}
}
public class EmailAddressAttribute : Attribute
{
// Validation logic implemented separately
}
class Program
{
static int Main(string[] args)
{
var parser = new Parser(with => with.HelpWriter = null);
var parserResult = parser.ParseArguments<AdvancedOptions>(args);
return parserResult
.MapResult(
(AdvancedOptions opts) => RunApplication(opts),
errs => DisplayHelp(parserResult, errs));
}
static int RunApplication(AdvancedOptions options)
{
try
{
// Custom validation
ValidateOptions(options);
Console.WriteLine("Application configuration:");
Console.WriteLine($" Input file: {options.InputFile}");
Console.WriteLine($" Output file: {options.OutputFile ?? "standard output"}");
Console.WriteLine($" Port: {options.Port}");
Console.WriteLine($" Output format: {options.Format}");
if (!string.IsNullOrEmpty(options.Email))
{
Console.WriteLine($" Email: {options.Email}");
}
if (options.Tags?.Any() == true)
{
Console.WriteLine($" Tags: {string.Join(", ", options.Tags)}");
}
if (options.Verbose)
{
Console.WriteLine("Verbose mode enabled");
}
else if (options.Quiet)
{
Console.WriteLine("Quiet mode enabled");
}
// Main processing
ProcessFiles(options);
return 0;
}
catch (ValidationException ex)
{
Console.WriteLine($"Validation error: {ex.Message}");
return 1;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return 1;
}
}
static void ValidateOptions(AdvancedOptions options)
{
// File existence check
if (!File.Exists(options.InputFile))
{
throw new ValidationException($"Input file does not exist: {options.InputFile}");
}
// Email address validation
if (!string.IsNullOrEmpty(options.Email))
{
var emailPattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
if (!Regex.IsMatch(options.Email, emailPattern))
{
throw new ValidationException("Please enter a valid email address");
}
}
// Port number validation
if (options.Port < 1024 || options.Port > 65535)
{
throw new ValidationException("Port number must be in range 1024-65535");
}
// Mutual exclusion check
if (options.Quiet && options.Verbose)
{
throw new ValidationException("--quiet and --verbose cannot be specified simultaneously");
}
}
static void ProcessFiles(AdvancedOptions options)
{
Console.WriteLine("Starting file processing...");
// Implement actual processing logic here
var inputContent = File.ReadAllText(options.InputFile);
if (!options.Quiet)
{
Console.WriteLine($"Input data size: {inputContent.Length} characters");
}
// Output processing
string output = ProcessContent(inputContent, options.Format);
if (!string.IsNullOrEmpty(options.OutputFile))
{
File.WriteAllText(options.OutputFile, output);
if (!options.Quiet)
{
Console.WriteLine($"Results saved to {options.OutputFile}");
}
}
else
{
Console.WriteLine(output);
}
}
static string ProcessContent(string content, OutputFormat format)
{
// Processing according to format
return format switch
{
OutputFormat.Json => $"{{\"content\": \"{content}\"}}",
OutputFormat.Xml => $"<content>{content}</content>",
OutputFormat.Csv => $"content\n\"{content}\"",
OutputFormat.Text => content,
_ => content
};
}
static int DisplayHelp<T>(ParserResult<T> result, IEnumerable<Error> errs)
{
var helpText = CommandLine.Text.HelpText.AutoBuild(result, h =>
{
h.AdditionalNewLineAfterOption = false;
h.Heading = "Advanced CLI Application v1.0.0";
h.Copyright = "Copyright (C) 2024 Example Corp.";
return CommandLine.Text.HelpText.DefaultParsingErrorsHandler(result, h);
}, e => e);
Console.WriteLine(helpText);
return 1;
}
}
public class ValidationException : Exception
{
public ValidationException(string message) : base(message) { }
}
Configuration File Integration
using CommandLine;
using System;
using System.IO;
using System.Text.Json;
public class ConfigurableOptions
{
[Option('c', "config", Required = false, HelpText = "Configuration file path")]
public string ConfigFile { get; set; }
[Option('h', "host", Required = false, HelpText = "Host name")]
public string Host { get; set; }
[Option('p', "port", Required = false, HelpText = "Port number")]
public int? Port { get; set; }
[Option('d', "database", Required = false, HelpText = "Database connection string")]
public string DatabaseConnection { get; set; }
[Option('v', "verbose", Required = false, HelpText = "Verbose logging")]
public bool Verbose { get; set; }
}
public class ConfigFile
{
public string Host { get; set; } = "localhost";
public int Port { get; set; } = 8080;
public string DatabaseConnection { get; set; }
public bool Verbose { get; set; }
}
class Program
{
static int Main(string[] args)
{
return Parser.Default.ParseArguments<ConfigurableOptions>(args)
.MapResult(
(ConfigurableOptions opts) => RunWithConfig(opts),
errs => 1);
}
static int RunWithConfig(ConfigurableOptions options)
{
try
{
// Load configuration file
var config = LoadConfig(options.ConfigFile);
// Override config file values with command-line arguments
var finalConfig = MergeConfig(config, options);
Console.WriteLine("Final configuration:");
Console.WriteLine($" Host: {finalConfig.Host}");
Console.WriteLine($" Port: {finalConfig.Port}");
Console.WriteLine($" Database: {finalConfig.DatabaseConnection ?? "not configured"}");
Console.WriteLine($" Verbose logging: {(finalConfig.Verbose ? "enabled" : "disabled")}");
// Start application
return StartApplication(finalConfig);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return 1;
}
}
static ConfigFile LoadConfig(string configPath)
{
if (string.IsNullOrEmpty(configPath))
{
configPath = "appsettings.json";
}
if (File.Exists(configPath))
{
Console.WriteLine($"Loading configuration file: {configPath}");
var json = File.ReadAllText(configPath);
return JsonSerializer.Deserialize<ConfigFile>(json);
}
else
{
Console.WriteLine("Configuration file not found. Using default settings.");
return new ConfigFile();
}
}
static ConfigFile MergeConfig(ConfigFile fileConfig, ConfigurableOptions cliOptions)
{
return new ConfigFile
{
Host = cliOptions.Host ?? fileConfig.Host,
Port = cliOptions.Port ?? fileConfig.Port,
DatabaseConnection = cliOptions.DatabaseConnection ?? fileConfig.DatabaseConnection,
Verbose = cliOptions.Verbose || fileConfig.Verbose
};
}
static int StartApplication(ConfigFile config)
{
Console.WriteLine("Starting application...");
// Actual application logic
if (config.Verbose)
{
Console.WriteLine("Running in verbose log mode");
}
return 0;
}
}
Project File Example
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="System.Text.Json" Version="6.0.0" />
</ItemGroup>
</Project>