Odin
Odin is a .NET library that provides a conventions based command-line parser. It's goal is to use the CLI equivalent of ASP .NET MVC's routing behavior to map command line arguments to methods and parameters. It differs from similar libraries in that it is designed to allow for an arbitrary n-depth command-tree.
Install / Use
/learn @crmckenzie/OdinREADME
Odin
In the .NET space there are a number of good libraries to handle run-of-the-mill command line argument parsing. My current favorite is a nuget package called simply [CommandLineParser]. So why write a new one?
Inspired By Thor
I've done some work with Ruby over the last couple of years and I was really impressed with the feature set offered by a ruby project called [thor]
In addition to a declarative approach to binding options to actions and arguments, thor supports the concept of subcommands.
We've seen subcommands used to great effect in command-line programs such as git and nuget, but current command line parser packages
offer little help with this feature.
Inspired by Convention Over Configuration
In ASP .NET MVC, urls are routed to the appropriate controller and action by convention. http://mysite.domain/Home/Index is understood to route to a controller called "Home" and invoke a method called "Index."
In addition, little manual wiring is required because ASP .NET MVC can discover and instantiate the controller easily at runtime.
I wondered if it would be possible to use a combination of reflection and convention to create a command-line application in C#.
Try it out!
Install-Package Odin-Commands
Contents
Features
Aliases
Odin supports aliases for parameters by means of the [Alias] attribute.
[Action(IsDefault=true)]
public void Generate(
[Alias("t")]
int target)
{
// do stuff
}
Descriptions
Odin supports extending the default help text by through the application of the [Description] attribute
to both parameters and actions.
[Action(IsDefault=true)]
[Description("Outputs the list of prime factors.")]
public void Generate(
[Alias("t")]
[Description("The target for which to calculate prime factors.")]
int target)
{
// do stuff
}
Default Parameter Values
Odin supports default values for parameters. If an explicit value for a parameter is not supplied in the command-line arguments, Odin will use the default value instead.
[Action(IsDefault=true)]
[Description("Outputs the list of prime factors.")]
public void Generate(
[Alias("t")]
[Description("The target for which to calculate prime factors.")]
int target = 1000)
{
// do stuff
}
Shared Parameters
Odin supports sharing parameters over several actions in the same command.
Simply declare a property at the class level and decorate it with the [Parameter] attribute.
[Description("Provides search capabilities for books.")]
public class SearchCommand : Command
{
[Parameter]
[Alias("s")]
[Description("The order to sort the results of the search.")]
public SortBooksBy SortBy { get; set; }
[Action]
[Description("Searches books by author.")]
public void Author(
[Alias("a")]
[Description("The author of the book being searched for.")]
string author)
{
Logger.Info("Find books by author '{0}'; sort results by '{1}'.\n", author, SortBy);
}
// snip
}
Sub Commands
Odin supports more complex command structures via sub commands. Simply register the sub command with the parent command and Odin will interpret the arguments appropriately.
public class BooksCommand : Command
{
public BooksCommand()
{
base.RegisterSubCommand(new SearchCommand());
}
// snip
}
Custom Conventions
Odin uses the KebabCase convention by default.
It also ships with SlashColon and SlashEquals conventions.
The SlashColon convention expectes arguments to be formatted as follows:
cli.exe action /argument:value
The SlashEquals convention expects arguments to be formatted as follows:
cle.exe action /argument=value
You can implement your own custom conventions by implementing IConvention, though if you do
please consider submitting them back to the project. Once you've implemented your own convention
you must register it with the root command in your command tree.
var command = new RootCommand().Use(new MyCustomConvention());
Custom Help Writer
If you would like to control the help documentation that Odin generates automatically,
you can create your own help text by implementing IHelpWriter.
Once you've written your own custom help writer you must register it with the root command of your command tree.
var command = new RootCommand().Use(new MyCustomHelpWriter());
Custom Logger
If you would like to control the logging facilitites that Odin provides,
you must implement the ILogger interface and register your implemention with the
root command of your command tree.
var command = new RootCommand().Use(new MyCustomLogger());
Samples
A Simple Example: FizzBuzz
Code
public class FizzBuzzCommand : Command
{
[Action(IsDefault = true)]
public void Play(int input)
{
if (input % 3 == 0 && input % 5 == 0)
{
Logger.Info("FizzBuzz");
}
else if (input % 3 == 0)
{
Logger.Info("Fizz");
}
else if (input % 5 == 0)
{
Logger.Info("Buzz");
}
else
{
Logger.Info(input.ToString());
}
Logger.Info("\n");
}
}
class Program
{
static void Main(string[] args)
{
var root = new FizzBuzzCommand();
var result = root.Execute(args);
Environment.Exit(result);
}
}
Invocations
With the above code, will attempt to map arguments to the appropriate method.
In order to expose a method to the CLI it must be marked with the Action attribute.
"play" maps to the FizzBuzzCommand.Play.
FizzBuzz.exe play --input 5
Buzz
It is not necessary to explicitly label the arguments in the command-line invocation. Odin will interpret arguments as ordered parameters if the parameter names are not specified.
FizzBuzz.exe play 5
Buzz
If the Action is marked as the Default action, it will not be necessary to specify the action name.
A Command implementation can only have 1 default action.
FizzBuzz.exe 5
Buzz
Help Text
default action: play
ACTIONS
help
--action-name default value:
The name of the action to provide help for.
play*
--input
To get help for actions
help <action>
A More Sophisticated Example: Prime Factors
Code
In this example we will see the impact of the [Alias] and [Description] attributes as well as the impact of parameters with default values.
class PrimeFactorsCommand : Command
{
[Action(IsDefault=true)]
[Description("Outputs the list of prime factors.")]
public void Generate(
[Alias("t")]
[Description("The target for which to calculate prime factors.")]
int target = 1000)
{
IList<int> primes = new List<int>();
for (var candidate = 2; target > 1; candidate++)
{
for (; target % candidate == 0; target /= candidate)
primes.Add(candidate);
}
var primesAsStrings= primes.Select(p => p.ToString()).ToArray();
var output = string.Join(", ", primesAsStrings);
Logger.Info(output);
}
}
class Program
{
static void Main(string[] args)
{
var root = new FizzBuzzCommand();
var result = root.Execute(args);
Environment.Exit(result);
}
}
Invocations
The target parameter has a default value of 1000 and an alias of '-t`.
The following invocations are equivalent.
PrimeFactors.exe generate --target 1000
2, 2, 2, 5, 5, 5
PrimeFactors.exe generate -t 1000
2, 2, 2, 5, 5, 5
PrimeFactors.exe generate
2, 2, 2, 5, 5, 5
PrimeFactors.exe 1000
2, 2, 2, 5, 5, 5
Help Text
default action: generate
ACTIONS
generate* Outputs the list of prime factors.
--target default value: 1000
aliases: -t
The target for which to calculate prime factors.
help
--action-name default value:
The name of the action to provide help for.
To get help for actions
help <action>
A Sophisticated Example: Books
In this example, we will see that Odin supports more complex command structures. This CLI supports adding and removing books to the library. It features a subcommand that allows us to search the library and sort the results.
Code
public class BooksCommand : Command
{
public BooksCommand()
{
base.RegisterSubCommand(new SearchCommand());
}
[Parameter]
[Alias("t")]
[Description("The title of the book being added.")]
public string Title { get; set; }
[Parameter]
[Alias("a")]
[Description("The author of the book being added
Related Skills
openpencil
2.1kThe world's first open-source AI-native vector design tool and the first to feature concurrent Agent Teams. Design-as-Code. Turn prompts into UI directly on the live canvas. A modern alternative to Pencil.
HappyColorBlend
HappyColorBlendVibe Project Guidelines Project Overview HappyColorBlendVibe is a Figma plugin for color palette generation with advanced tint/shade blending capabilities. It allows designers to
Flyaro-waffle-app
Waffle Delight - Full Stack MERN Application Rules & Documentation Project Overview A comprehensive waffle delivery application built with MERN stack featuring premium UI/UX, admin management, a
ui-ux-pro-max-skill
60.7kAn AI SKILL that provide design intelligence for building professional UI/UX multiple platforms
