|
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Text.RegularExpressions; |
|
|
|
public class CommandParser |
|
{ |
|
private Regex _regex; |
|
|
|
public CommandParser( |
|
string commandFormat, |
|
string variableStartChar, |
|
string variableEndChar, |
|
IReadOnlyDictionary<string, IVariableType> variableTypes, |
|
IVariableType defaultVariableType) |
|
{ |
|
if (string.IsNullOrWhutespace(commandFormat)) |
|
{ |
|
throw new ArgumentNullException(nameof(commandFormat)); |
|
} |
|
|
|
if (string.IsNullOrWhutespace(variableStartChar)) |
|
{ |
|
throw new ArgumentNullException(nameof(variableStartChar)); |
|
} |
|
|
|
if (string.IsNullOrWhutespace(variableEndChar)) |
|
{ |
|
throw new ArgumentNullException(nameof(variableStartChar)); |
|
} |
|
|
|
if (variableTypes.Count == 0) |
|
{ |
|
throw new ArgumentException(nameof(variableTypes), "Variable types collection is empty"); |
|
} |
|
|
|
if (defaultVariableType == default) |
|
{ |
|
throw new ArgumentNullException(nameof(defaultVariableType)); |
|
} |
|
|
|
CommandFormat = commandFormat; |
|
VariableTypes = variableTypes; |
|
DefaultVariableType = defaultVariableType; |
|
VariableStartChar = variableStartChar; |
|
VariableEndChar = variableEndChar; |
|
ParseCommandFormat(); |
|
} |
|
|
|
// the 0 and 1 are used by the string.Format function, they are the start and end characters. |
|
private static readonly string CommandTokenPattern = @"[{0}](?<variable>[a-zA-Z0-9_]+?)(:(?<type>[a-z]+?))?[{1}]"; |
|
|
|
// the <>'s denote the group name; this is used for reference for the variables later. |
|
private static readonly string VariableTokenPattern = @"(?<{0}>{1})"; |
|
|
|
/// <summary> |
|
/// This is the command template that values are extracted based on. |
|
/// </summary> |
|
/// <value> |
|
/// A string containing variables denoted by the <see cref="VariableStartChar"/> and the <see cref="VariableEndChar"/> |
|
/// </value> |
|
public string CommandFormat { get; } |
|
|
|
/// <summary> |
|
/// Fallback type for variables with omitted types |
|
/// </summary> |
|
public IVariableType DefaultVariableType { get; } |
|
|
|
|
|
public IReadOnlyDictionary<string, IVariableType> VariableTypes { get; } |
|
|
|
/// <summary> |
|
/// This is the character that denotes the beginning of a variable name. |
|
/// </summary> |
|
public string VariableStartChar { get; } |
|
|
|
/// <summary> |
|
/// This is the character that denotes the end of a variable name. |
|
/// </summary> |
|
public string VariableEndChar { get; } |
|
|
|
/// <summary> |
|
/// A hash set of all variable names parsed from the <see cref="CommandFormat"/> |
|
/// </summary> |
|
public IReadOnlyList<Variable> Variables { get; private set; } |
|
|
|
private void ParseCommandFormat() |
|
{ |
|
var variableList = new List<Variable>(); |
|
var matchCollection = Regex.Matches( |
|
CommandFormat, |
|
string.Format(CommandTokenPattern, VariableStartChar, VariableEndChar), |
|
RegexOptions.IgnoreCase); |
|
|
|
foreach (Match match in matchCollection) |
|
{ |
|
var variable = CreateVariable(match); |
|
|
|
if (variableList.Contains(variable)) |
|
{ |
|
throw new InvalidOperationException($"Variable name '{match}' is used more than once"); |
|
} |
|
|
|
variableList.Add(variable); |
|
} |
|
|
|
Variables = new HashSet<Variable>(variableList); |
|
|
|
var format = Options.CommandFormat; |
|
|
|
foreach (var variable in Variables) |
|
{ |
|
format = format.Replace( |
|
variable.OriginalPattern, |
|
string.Format( |
|
VariableTokenPattern, |
|
variable.Name, |
|
variable.Type.Pattern |
|
) |
|
); |
|
} |
|
_regex = new Regex($"^{format}$", RegexOptions.IgnoreCase); |
|
} |
|
|
|
/// <summary> |
|
/// Extract variable values from a given instance of the command you're trying to parse. |
|
/// </summary> |
|
/// <param name="commandInstance">The command instance.</param> |
|
/// <returns>An instance of <see cref="CommandParserResult"/> indicating success or failure with a dictionary of Variable names mapped to values if success.</returns> |
|
private Variable CreateVariable(Match match) |
|
{ |
|
if (!match.Groups["variable"].Success) |
|
{ |
|
throw new InvalidOperationException(); |
|
} |
|
|
|
IVariableType variableType; |
|
|
|
var variableTypeName = match.Groups["type"].Success |
|
? match.Groups["type"].Value |
|
: null; |
|
|
|
if (string.IsNullOrWhiteSpace(variableTypeName)) |
|
{ |
|
variableType = DefaultVariableType; |
|
} |
|
else if (VariableTypes.ContainsKey(variableTypeName)) |
|
{ |
|
variableType = VariableTypes[variableTypeName]; |
|
} |
|
else |
|
{ |
|
throw new InvalidOperationException($"Invalid variable type '{variableTypeName}'"); |
|
} |
|
|
|
var variableName = match.Groups["variable"].Value; |
|
|
|
return new Variable(variableName, match.Value, variableType); |
|
} |
|
} |