Skip to content

bodong1987/MiniCommandLineParser

Repository files navigation

MiniCommandLineParser

NuGet License .NET

A simple, lightweight, and dependency-free command-line parsing library for .NET.

🎯 Why MiniCommandLineParser?

Feature MiniCommandLineParser Other Libraries
Zero Dependencies βœ… No external packages ❌ Often require multiple dependencies
Lightweight βœ… Minimal footprint ❌ Can be bloated
Bidirectional βœ… Parse & Format ❌ Usually parse-only
Learning Curve βœ… Simple, intuitive API ❌ Complex configurations
Multi-target βœ… .NET 6/7/8/9 + Standard 2.1 ⚠️ Varies

✨ Features

  • πŸͺΆ Lightweight - Minimal footprint with zero external dependencies
  • 🎯 Simple API - Intuitive attribute-based configuration
  • πŸ“¦ Multi-target - Supports .NET 6.0, 7.0, 8.0, 9.0 and .NET Standard 2.1
  • πŸ”„ Bidirectional - Parse arguments to objects AND format objects back to command-line strings
  • πŸ“ Auto Help Text - Built-in help text generation with customizable formatting
  • πŸ”§ Flexible - Supports short/long options, positional arguments, arrays, enums, flags, and more
  • 🧡 Thread-Safe - Type information caching is thread-safe for concurrent usage
  • ⚑ High Performance - Efficient parsing with minimal allocations
  • πŸ“ Positional Arguments - Support for index-based arguments like git clone <url>
  • πŸ”— Custom Separators - Define custom separators for array values (e.g., --tags=a;b;c)
  • βœ… Boolean Flexibility - Multiple syntaxes: --flag, --flag=true, --flag true

πŸ“₯ Installation

Package Manager Console

Install-Package MiniCommandLineParser

.NET CLI

dotnet add package MiniCommandLineParser

PackageReference

<PackageReference Include="MiniCommandLineParser" Version="*" />

πŸš€ Quick Start

Step 1: Define Your Options Class

using MiniCommandLineParser;

public class Options
{
    [Option('i', "input", Required = true, HelpText = "Input file path")]
    public string InputFile { get; set; }

    [Option('o', "output", HelpText = "Output file path")]
    public string OutputFile { get; set; }

    [Option('v', "verbose", HelpText = "Enable verbose output")]
    public bool Verbose { get; set; }

    [Option("count", HelpText = "Number of iterations")]
    public int Count { get; set; } = 1;
}

Step 2: Parse Command-Line Arguments

using MiniCommandLineParser;

class Program
{
    static void Main(string[] args)
    {
        var result = Parser.Default.Parse<Options>(args);

        if (result.Result == ParserResultType.Parsed)
        {
            var options = result.Value;
            Console.WriteLine($"Input: {options.InputFile}");
            Console.WriteLine($"Output: {options.OutputFile}");
            Console.WriteLine($"Verbose: {options.Verbose}");
            Console.WriteLine($"Count: {options.Count}");
        }
        else
        {
            Console.WriteLine($"Error: {result.ErrorMessage}");
        }
    }
}

Step 3: Run Your Application

myapp --input data.txt --output result.txt --verbose --count 5
# or using short names
myapp -i data.txt -o result.txt -v --count 5

πŸ“– Detailed Usage

Option Attribute

The [Option] attribute is used to mark properties as command-line options:

public class Options
{
    // Short name only (property name becomes long name)
    [Option('v')]
    public bool Verbose { get; set; }

    // Long name only
    [Option("configuration")]
    public string Config { get; set; }

    // Both short and long names
    [Option('o', "output")]
    public string OutputPath { get; set; }

    // No parameters (property name becomes long name)
    [Option]
    public string DefaultName { get; set; }

    // Required option
    [Option('i', "input", Required = true, HelpText = "Required input file")]
    public string Input { get; set; }

    // With help text
    [Option("timeout", HelpText = "Timeout in seconds")]
    public int Timeout { get; set; } = 30;

    // Positional argument (Index >= 0 makes it positional)
    [Option(Index = 0, MetaName = "COMMAND", HelpText = "The command to execute")]
    public string Command { get; set; }

    // Array with custom separator
    [Option("tags", Separator = ';', HelpText = "Tags separated by semicolon")]
    public List<string> Tags { get; set; }
}

Supported Argument Formats

MiniCommandLineParser supports various argument formats:

# Short options
-v -i input.txt

# Long options
--verbose --input input.txt

# Equals syntax
--input=input.txt
-i=input.txt

# Quoted values (for paths with spaces)
--output "my output file.txt"
--path="C:\Program Files\MyApp"

# Boolean flags - multiple equivalent syntaxes
--verbose           # Sets Verbose = true (flag presence)
--verbose=true      # Sets Verbose = true (equals syntax)
--verbose true      # Sets Verbose = true (space syntax)
--verbose=false     # Sets Verbose = false
--verbose false     # Sets Verbose = false
-v                  # Short form, sets Verbose = true
-v=true             # Short form with equals
-v false            # Short form with space

# Positional arguments (no option prefix needed)
myapp clone http://example.com --verbose
# Where 'clone' is Index=0 and 'http://example.com' is Index=1

# Array with custom separator (using equals syntax)
--tags=dev;test;prod    # Parsed using ';' separator
--ids=1,2,3,4           # Parsed using ',' separator

Array/List Support

Support for collection types like List<T> and arrays:

public class Options
{
    [Option("files", HelpText = "Input files to process")]
    public List<string> Files { get; set; }

    [Option("numbers", HelpText = "Numbers to sum")]
    public int[] Numbers { get; set; }

    // With custom separator - elements can be passed in a single value
    [Option("tags", Separator = ';', HelpText = "Tags separated by semicolon")]
    public List<string> Tags { get; set; }

    [Option("ids", Separator = ',', HelpText = "IDs separated by comma")]
    public List<int> Ids { get; set; }
}

Usage:

# Space-separated (default)
myapp --files file1.txt file2.txt file3.txt --numbers 1 2 3 4 5

# Using custom separator with equals syntax
myapp --tags=dev;test;prod --ids=1,2,3,4,5

# Mixed: separator works with both syntaxes
myapp --tags dev;test;prod
myapp --tags "dev;test;prod"

Enum Support

Both regular enums and flags enums are supported:

public enum LogLevel
{
    Debug,
    Info,
    Warning,
    Error
}

[Flags]
public enum Features
{
    None = 0,
    Logging = 1,
    Caching = 2,
    Compression = 4,
    All = Logging | Caching | Compression
}

public class Options
{
    [Option("level", HelpText = "Log level")]
    public LogLevel Level { get; set; } = LogLevel.Info;

    [Option("features", HelpText = "Enabled features")]
    public Features EnabledFeatures { get; set; }
}

Usage:

# Regular enum
myapp --level Warning

# Flags enum (multiple values)
myapp --features Logging Caching Compression

Positional Arguments

Positional arguments are values that don't require an option prefix. They are identified by their position in the command line, similar to how git clone <url> works.

public class GitCloneOptions
{
    // Index = 0 means this is the first positional argument
    [Option(Index = 0, MetaName = "COMMAND", HelpText = "Git command to execute")]
    public string Command { get; set; } = "";

    // Index = 1 means this is the second positional argument
    [Option(Index = 1, MetaName = "URL", HelpText = "Repository URL to clone")]
    public string Url { get; set; } = "";

    // Regular named options still work alongside positional arguments
    [Option('v', "verbose", HelpText = "Enable verbose output")]
    public bool Verbose { get; set; }

    [Option("depth", HelpText = "Create a shallow clone with specified depth")]
    public int Depth { get; set; }
}

Usage:

# Positional arguments come first (or can be mixed with named options)
myapp clone https://github.com/user/repo.git --verbose --depth 1

# Named options can appear before positional arguments too
myapp --verbose clone https://github.com/user/repo.git --depth 1

# The parser correctly identifies positional vs named arguments

Key Points:

  • Use Index property to define positional arguments (Index >= 0)
  • MetaName provides a display name for help text (e.g., "URL" instead of the property name)
  • Positional arguments are matched in order by their Index value
  • Named options (with - or -- prefix) can appear anywhere in the command line
  • Positional and named options can be freely mixed

Supported Data Types

Type Example Notes
string --name "John Doe" Supports quoted values
int, long, short --count 42 All integer types
float, double, decimal --rate 3.14 Floating-point types
bool --verbose or --flag=true Flag presence = true; also supports --flag true
enum --level Info Case-insensitive by default
[Flags] enum --flags A B C Multiple space-separated values
List<T>, T[] --items a b c Any supported element type
Arrays with separator --tags=a;b;c Use Separator property in attribute

βš™οΈ Parser Configuration

Custom Parser Settings

var parser = new Parser(new ParserSettings
{
    // Case-sensitive option matching (default: false)
    CaseSensitive = false,
    
    // Ignore unknown arguments (default: true)
    IgnoreUnknownArguments = true
});

var result = parser.Parse<Options>(args);

Parser Settings Reference

Setting Default Description
CaseSensitive false When false, --Input matches --input
IgnoreUnknownArguments true When true, unknown args are silently ignored

πŸ”„ Bidirectional Conversion

Format Object to Command Line

A unique feature of MiniCommandLineParser is the ability to convert options objects back to command-line strings:

var options = new Options
{
    InputFile = "data.txt",
    OutputFile = "result.txt",
    Verbose = true,
    Count = 5
};

// Complete format - includes all options (space-separated style)
string complete = Parser.FormatCommandLine(options, CommandLineFormatMethod.Complete);
// Output: --input data.txt --output result.txt --verbose True --count 5

// Simplified format - only non-default values
string simplified = Parser.FormatCommandLine(options, CommandLineFormatMethod.Simplify);
// Output: --input data.txt --output result.txt --verbose True --count 5

// Equal sign style - uses --option=value syntax
string equalStyle = Parser.FormatCommandLine(options, CommandLineFormatMethod.EqualSignStyle);
// Output: --input=data.txt --output=result.txt --verbose=True --count=5

// Combine flags: Simplify + EqualSignStyle
string combined = Parser.FormatCommandLine(options, 
    CommandLineFormatMethod.Simplify | CommandLineFormatMethod.EqualSignStyle);
// Output: --input=data.txt --output=result.txt --verbose=True --count=5

// Get as string array
string[] args = Parser.FormatCommandLineArgs(options, CommandLineFormatMethod.Simplify);

Array Formatting

Arrays are formatted differently based on the style:

public class BuildOptions
{
    [Option("tags", Separator = ';')]
    public List<string> Tags { get; set; }
    
    [Option("ids", Separator = ',')]
    public int[] Ids { get; set; }
}

var options = new BuildOptions 
{ 
    Tags = new List<string> { "dev", "test", "prod" },
    Ids = new[] { 1, 2, 3 }
};

// Space-separated style (Complete/Simplify without EqualSignStyle)
Parser.FormatCommandLine(options, CommandLineFormatMethod.Complete);
// Output: --tags dev test prod --ids 1 2 3

// Equal sign style - arrays automatically use their defined separator
Parser.FormatCommandLine(options, CommandLineFormatMethod.EqualSignStyle);
// Output: --tags=dev;test;prod --ids=1,2,3

Format Method Flags

Flag Description
None Default space-separated style
Complete Output all options including default values
Simplify Only output options that differ from defaults
EqualSignStyle Use --option=value syntax; arrays use their defined separator

Note: Flags can be combined using the | operator for flexible output formatting.

Use Cases

  • Configuration persistence: Save/restore command-line configurations
  • Process spawning: Launch child processes with the same options
  • Logging/Debugging: Log the effective command-line for diagnostics
  • Testing: Generate test command lines programmatically

πŸ“ Help Text Generation

Auto-Generate Help Text

var options = new Options();
string helpText = Parser.GetHelpText(options);
Console.WriteLine(helpText);

Output example:

    -i, --input                                 [Required] Input file path
    -o, --output                                [Optional] Output file path
    -v, --verbose                               [Optional] Enable verbose output
    --count                                     [Optional] Number of iterations
    --level                                     [Optional,Enum] Log level
                                                --level Debug Info Warning Error
    --features                                  [Optional,Flags] Enabled features
                                                --features Logging Caching Compression

Custom Formatter

Implement IFormatter for custom help text formatting:

public interface IFormatter
{
    void Append(StringBuilder stringBuilder, string name, string attributes, 
                string? helpText, string? usage);
}
// Use custom formatter
var customFormatter = new MyCustomFormatter();
string helpText = Parser.GetHelpText(options, customFormatter);

Formatting Options

// Default indent and blank spaces
Parser.DefaultIndent  // 4 spaces for left indentation
Parser.DefaultBlank   // 43 characters for option name column width

πŸ” API Reference

Parser Class

Method Description
Parse<T>(string arguments) Parse a command-line string
Parse<T>(IEnumerable<string> arguments) Parse an array of arguments
Parse<T>(string arguments, T value) Parse into an existing instance
FormatCommandLine(object, CommandLineFormatMethod) Convert object to command-line string
FormatCommandLineArgs(object, CommandLineFormatMethod) Convert object to string array
GetHelpText(object, IFormatter?) Generate help text
GetTypeInfo<T>() Get cached type metadata

Static Members

Member Description
Parser.Default Default parser instance with default settings

ParserResult

Property Description
Result Parsed or NotParsed
Value The parsed options object
ErrorMessage Error details if parsing failed
Type Type metadata information

OptionAttribute

Property Description
ShortName Single character option (e.g., 'v' for -v)
LongName Full option name (e.g., "verbose" for --verbose)
Required Whether the option must be provided
HelpText Description shown in help output
Index Positional argument index (>= 0 makes it positional, default: -1)
MetaName Display name for positional arguments in help text
Separator Custom separator character for array/list values (default: none)

CommandLineFormatMethod (Flags Enum)

Flag Value Description
None 0 Default space-separated style
Complete 1 Output all options including defaults
Simplify 2 Only output non-default values
EqualSignStyle 4 Use --option=value syntax

πŸ’‘ Best Practices

1. Use Meaningful Option Names

// Good
[Option('o', "output", HelpText = "Output file path")]
public string OutputPath { get; set; }

// Avoid
[Option('x', "x")]
public string X { get; set; }

2. Provide Default Values

public class Options
{
    [Option("timeout", HelpText = "Request timeout in seconds")]
    public int Timeout { get; set; } = 30;  // Sensible default

    [Option("retries", HelpText = "Number of retry attempts")]
    public int Retries { get; set; } = 3;
}

3. Always Check Parse Result

var result = Parser.Default.Parse<Options>(args);

if (result.Result != ParserResultType.Parsed)
{
    Console.Error.WriteLine(result.ErrorMessage);
    Console.WriteLine(Parser.GetHelpText(new Options()));
    return 1;  // Exit with error code
}

// Safe to use result.Value here

4. Use Required for Mandatory Options

[Option('i', "input", Required = true, HelpText = "Input file (required)")]
public string InputFile { get; set; }

πŸ§ͺ Example Project

Here's a complete example demonstrating all features:

using MiniCommandLineParser;

public enum OutputFormat { Text, Json, Xml }

[Flags]
public enum ProcessingFlags
{
    None = 0,
    Validate = 1,
    Transform = 2,
    Compress = 4
}

public class Options
{
    // Positional argument - the command to execute
    [Option(Index = 0, MetaName = "COMMAND", HelpText = "Command to execute (process, convert, analyze)")]
    public string Command { get; set; } = "";

    // Positional argument - the input file
    [Option(Index = 1, MetaName = "INPUT", HelpText = "Input file path")]
    public string InputFile { get; set; } = "";

    [Option('o', "output", HelpText = "Output file path")]
    public string OutputFile { get; set; }

    [Option('f', "format", HelpText = "Output format")]
    public OutputFormat Format { get; set; } = OutputFormat.Text;

    [Option("flags", HelpText = "Processing flags")]
    public ProcessingFlags Flags { get; set; } = ProcessingFlags.Validate;

    // Array with custom separator
    [Option("tags", Separator = ';', HelpText = "Tags for the output (semicolon-separated)")]
    public List<string> Tags { get; set; }

    [Option("include", HelpText = "Additional files to include")]
    public List<string> IncludeFiles { get; set; }

    [Option('v', "verbose", HelpText = "Enable verbose logging")]
    public bool Verbose { get; set; }

    [Option("threads", HelpText = "Number of worker threads")]
    public int ThreadCount { get; set; } = Environment.ProcessorCount;
}

class Program
{
    static int Main(string[] args)
    {
        // Show help if requested
        if (args.Length == 0 || args.Contains("--help") || args.Contains("-h"))
        {
            Console.WriteLine("Usage: myapp <command> <input> [options]");
            Console.WriteLine();
            Console.WriteLine("Options:");
            Console.WriteLine(Parser.GetHelpText(new Options()));
            return 0;
        }

        // Parse arguments
        var result = Parser.Default.Parse<Options>(args);

        if (result.Result != ParserResultType.Parsed)
        {
            Console.Error.WriteLine($"Error: {result.ErrorMessage}");
            return 1;
        }

        var options = result.Value;

        // Display parsed options
        if (options.Verbose)
        {
            Console.WriteLine("Parsed options:");
            Console.WriteLine($"  Command: {options.Command}");
            Console.WriteLine($"  Input: {options.InputFile}");
            Console.WriteLine($"  Output: {options.OutputFile ?? "(stdout)"}");
            Console.WriteLine($"  Format: {options.Format}");
            Console.WriteLine($"  Flags: {options.Flags}");
            Console.WriteLine($"  Threads: {options.ThreadCount}");
            
            if (options.Tags?.Count > 0)
                Console.WriteLine($"  Tags: {string.Join(", ", options.Tags)}");
            
            if (options.IncludeFiles?.Count > 0)
                Console.WriteLine($"  Includes: {string.Join(", ", options.IncludeFiles)}");
        }

        // Format back to command line for logging
        Console.WriteLine($"Effective command: {Parser.FormatCommandLine(options, CommandLineFormatMethod.Simplify | CommandLineFormatMethod.EqualSignStyle)}");

        // Your application logic here...
        
        return 0;
    }
}

Run examples:

# Using positional arguments
myapp process data.txt -o result.json -f Json -v

# Positional args with named options mixed
myapp --verbose process data.txt --format=Xml --threads=8

# With custom separator for tags
myapp convert input.csv --tags=important;reviewed;final -o output.json

# With flags and includes
myapp analyze report.txt --flags Validate Transform --include extra1.txt extra2.txt

# Using equals syntax throughout
myapp process data.txt -o=result.json --format=Json --threads=4 --tags=dev;test

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

πŸ“¬ Contact


Made with ❀️ for the .NET community

About

A simple, lightweight C# command-line parsing library.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages