How to implement custom command line & execution - c#

I'm trying to build a custom commandline for my app, i have several basic commands, and i simply use bunch of "if" statements to check what the command is. currently it looks something like this
public void ExecuteCommand()
{
string input = ReadLine(); //gets last string from input
bool isDone = false; //need bool to check whether command was executed or no, by default false.
Match result = Regex.Match(input, #"([^\s]+)"); //to get command name
string commandName = result.Value.ToLower();
string value = Regex.Match(input, #"\s(.*)").Value; //to get its parameter. currently everything after ' ' space.
if (commandName == "close")
{
Close(); isDone = true;
}
//so commandline is separate window, and appendedForm is a main form. in which some functions are executed.
if (commandName == "exit")
{
appendedForm.Close();
}
if (commandName == "spoof")
{
appendedForm.Fn_Spoof();
isDone = true;
}
if(commandName == "spoofstop")
{
appendedForm.Fn_StopCapture();
isDone = true;
}
if(commandName == "scan")
{
appendedForm.Fn_Scan(); isDone = true;
}
if(commandName == "clear")
{
output.Text = "";
WriteLine("Console cleared. Cache is empty.");
//data_lines.Clear();
isDone = true;
}
...
}
So that's basically it. I have a mainForm, and commandline form. string input is typed into commandline, then I check the name of command and execute some function from mainForm.
My question is, what is the best way of implementing such kind of thing? I surely can just continue writing bunch of "if"s, but something tells me that it's not the best way to make it.
I've thought of creating class "Command"
public class Command
{
public string name;
public string description;
public bool hasParameter;
Command()
{
}
}
And storing all commands in some sort of array, but I am not sure how would I use this to call a function from mainForm.
Any ideas are welcome!

You could stuff all commands into a Dictionary<string, someDelegate>; if you can live with all commands having the same return type.
I have used string and set up a few commands.
I make use of the params keyword to avoid the ugly new object[] on each call.
You still need to cast the arguments, unless you can make them all one type. (Which may actually be not such a bad idea, as they all come from an input string..)
Here is an example:
public delegate string cmdDel(params object[] args);
Dictionary<string, cmdDel> cmd = new Dictionary<string, cmdDel>();
Add a few functions:
cmd.Add("clear", cmd_clear);
cmd.Add("exit", cmd_exit);
cmd.Add("add", cmd_add);
cmd.Add("log", cmd_log);
With these bodies:
public string cmd_clear(params object[] args)
{
return "cleared";
}
public string cmd_exit(params object[] args)
{
return "exit";
}
public string cmd_add(params object[] args)
{
return ((int)args[0] + (int)args[1]).ToString();
}
public string cmd_log(params object[] args)
{
StringBuilder log = new StringBuilder();
foreach (object a in args) log.Append(a.ToString() + " ");
return log.ToString();
}
And test:
Console.WriteLine(cmd["clear"]());
Console.WriteLine(cmd["add"]( 23, 42));
Console.WriteLine(cmd["log"]( 23, "+" + 42, "=", cmd["add"]( 23, 42) ));
Console.WriteLine(cmd["exit"]());
cleared
65
23 + 42 = 65
exit
Of course you still need to use (at least) as many lines for setup as you have commands. And also need to do a similar amount of error checking.
But the command processing part can get pretty simple.

Related

creating global string array through its own class, but still not recognized in different class method C#

Created a simple class with one element, a string [] array and filled it with elements. I want to be able to add and delete elements in this array anywhere in my application, but it is not accessable. been trying variations for two days so coming to the best for help.
enter code here
public static void TestSlots()
{
String currentBehavior = _core.GetVariable("$_current_name", false);
bool success1 = _core.ApiApp().SetDiagOutput("MYWARNING " + currentBehavior + " is");
int indexNumber = MedIntents.medIntents.IndexOf(currentBehavior);
;
if (indexNumber < 0)
{
return;
}
else
{
//Global.MedIntents.RemoveAt(indexNumber);
bool success = _core.ApiApp().SetDiagOutput("MYWARNING " + currentBehavior + " removed");
}
//now check if they asked or hit more than one important entity
return;
}
// Console.WriteLine("Hello World!");
}
internal class MedIntents
{
public string[] medIntents = new string[] {
"any_chills",
"constant_or_intermittent",
"gradual_or_sudden",
"had_them_before",
"how_often",
"howdoesitstart",
"hurt_elsewhere",
"nausea_or_vomitting",
"numbness",
"pain_relief",
"relation_to_food_or_medical",
"scaleofone2ten",
"warning_signs"
};
medIntents is an instance member. You may want to convert it to a static member. To prevent modification, you can add the readonly modifier. – Crafted Pod 2 hours ago

How to keep prompting for input until it is valid?

I'm trying to make it so when the given answer is neither 1 nor 2 the message "Please enter a valid answer." shows up and it goes back to the question.
Here's my code:
Coloration(ConsoleColor.DarkMagenta, "What do you want to do? [1/2]");
Console.WriteLine("1. Draw");
Console.WriteLine("2. Stay");
int i = 0;
string input1 = Console.ReadLine();
// If answer is not 2, go through this, if answer is 2 continue
if (input1 != "2")
{
// If answer is 1 add 1 to i
if (input1 == "1")
{
i++;
}
// If answer is neither 1 nor 2; go back to question
if (input1 != "1" || input1 != "2")
{
Coloration(ConsoleColor.Red, "Please enter a valid answer.");
}
}
You want something more like this...
Console.WriteLine("What do you want to do? [1/2]");
Console.WriteLine("1. Draw");
Console.WriteLine("2. Stay");
int userChoice = 0;
bool validInput = false;
while (!validInput)
{
Console.WriteLine();
Console.WriteLine("Enter choice [1/2]...");
string input = Console.ReadLine();
string trimmedInput = input.Trim();
if (trimmedInput == "1" || trimmedInput == "2")
{
validInput = true;
userChoice = Int32.Parse(trimmedInput);
}
}
// We leave the while loop here once validInput == true
// Now take action based on userChoice
Console.WriteLine("You chose " + userChoice);
Console.ReadLine();
As others have noted, you need to keep watching user input. You have your answer but I wanted to also introduce you to the concept of a Read Eval Print Loop (AKA a REPL). Your current solution is not going to scale well. Take a look at the following implementation of your desired UI:
using System;
using System.Collections.Generic;
namespace MyProgram
{
class Program
{
// Don't actually use inner classes. This is just for demonstration purposes.
class Command
{
public Command(string description, Action action)
{
this.Description = description;
this.Action = action;
}
public string Description { get; private set; }
public Action Action { get; private set; }
}
static void Main(string[] args)
{
// Create a dictionary of commands, mapped to the input that evokes each command.
var availableCommands = new Dictionary<string, Command>();
// Note that since we are storing the descriptions / commands in one place, it makes
// changing a description or adding/modifying a command trivial. Want "Draw" to be invoked
// by "d" instead of "1"? Change it here and you're done.
availableCommands.Add("1", new Command("Draw", Draw));
availableCommands.Add("2", new Command("Stay", Stay));
// This command demonstrates how to use a lambda as an action if you so desire.
availableCommands.Add("3", new Command("Exit", () => System.Environment.Exit(1)));
// Build command list string
var cmdList = string.Join('/', availableCommands.Keys);
// Display welcome message
Coloration(ConsoleColor.DarkMagenta, $"What do you want to do? [{cmdList}]");
// Show user initial list of commands
DisplayAvailableCommands(availableCommands);
// Read Eval Print Loop (REPL).
while (true)
{
var userInput = Console.ReadLine();
// If the user entered a valid command, execute it.
if (availableCommands.ContainsKey(userInput))
{
availableCommands[userInput].Action();
// If you want the user to be able to perform additional actions after their initial successful
// action, don't return here.
return;
}
// Otherwise, let them know they didn't enter a valid command and show them a list of commands
else
{
Coloration(ConsoleColor.Red, "Please enter a valid answer.");
DisplayAvailableCommands(availableCommands);
}
}
}
// I'm just assuming your coloration method looks something like this...
static void Coloration(ConsoleColor color, string message)
{
// Keep track of original color so we can set it again.
var originalColor = Console.ForegroundColor;
Console.ForegroundColor = color;
Console.WriteLine(message);
Console.ForegroundColor = originalColor;
}
static void DisplayAvailableCommands(Dictionary<string, Command> commands)
{
// We always want a line above the commands
Console.WriteLine("Available commands:");
foreach (string key in commands.Keys)
{
var command = commands[key];
Console.WriteLine($"{key}. {command.Description}");
}
}
static void Draw()
{
Coloration(ConsoleColor.DarkGreen, "You chose to draw!");
}
static void Stay()
{
Coloration(ConsoleColor.DarkGreen, "You chose to stay!");
}
}
}
Adding new commands will be a breeze. And you can further refactor this to even smaller single-purpose methods. Each command could have its' own class in the event that your commands become more complicated (and in a real app, they will. Trust me). You can refactor the REPL to its' own class/method as well (take a look at this implementation I did a while back for example).
Anyway, this is my $.02 on a more correct way you could build a user interface of this nature. Your current solution is going to work, but it's not going to be fun to work with long-term. Hopefully this helps.
use a while loop to continuously ask for input if not valid, then check the input with a switch statement
bool flag = false;
while(!flag)
{
switch(input1)
{
case "1":
flag = true;
break;
case "2":
flag = true;
break;
default:
Coloration(ConsoleColor.Red, "Please enter a valid answer.");
break;
}
}

Obtaining original variable name from within an extension method

We are currently working on a logging solution and have implemented an extension method call 'Log'. When writing to the log file, we would ideally like to write the original variable name (rather than the variable name used in the extension method).
What we are currently having to do for this is:
public void DoSomeWork()
{
String testString = "Hey look I'm a string!";
testString.Log("testString value");
}
With the extention method:
public static String Log(this String valueToStore, String name)
{
// The logging code which uses the 'name' parameter
}
The issue here is that it becomes difficult to read on longer lines of code and looks clustered. What would be ideal is this:
public void DoSomeWork()
{
String testString = "Hey look I'm a string!";
testString.Log();
}
With the extension method:
public static String Log(this String valueToStore)
{
// The logging code which is able to retrieve the
// value 'testString' from the 'valueToStore' value
}
Is this at all possible by using Reflection? I'm aware of the nameofoption, but that only returns the string 'valueToStore' when used in the extension method.
Well, short answer is no. The variable names are not guaranteed to persist after compilation in unchanged form. That information would have to be somehow persisted (for example by the use of nameof()). Also, the variable name might not exist ("test".GetVarName()).
The long answer is: yes, possibly, but it's one of the most ridiculous things I've created in my life:
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Test1
{
class Program
{
static void Main(string[] args)
{
var myVarName = "test";
myVarName.Test();
Console.ReadKey();
}
}
static class Extensions
{
public static void Test(
this string str,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0
)
{
var relevantLine = File.ReadAllLines(sourceFilePath)[sourceLineNumber-1];
var currMethodName = MethodInfo.GetCurrentMethod().Name;
var callIndex = relevantLine.IndexOf(currMethodName + "()");
var sb = new Stack<char>();
for (var i = callIndex - 2; i >= 0; --i)
{
if (Char.IsLetterOrDigit(relevantLine[i]))
{
sb.Push(relevantLine[i]);
}
}
Console.WriteLine(new String(sb.ToArray()));
}
}
}
C# 10 has CallerArgumentExpressionAttribute that will do just that
public static void PrintNameAndValue(
this object obj,
[System.Runtime.CompilerServices.CallerArgumentExpression("obj")] string callerExp = ""
)
{
Console.WriteLine(callerExp + " = " + obj.ToString());
}
It'll capture the entire expression passed:
public void TestPrintNameAndValue()
{
string mystring = "test";
int myint = 5;
mystring.PrintNameAndValue(); // mystring = test
myint.PrintNameAndValue(); // myint = 5
(myint + 10).PrintNameAndValue(); // myint + 10 = 15
mystring.ToUpper().PrintNameAndValue(); // mystring.ToUpper() = TEST
}
You can use an Expression to achieve that, but performance-wise it may not be the best option:
public static void Log<T>(Expression<Func<T>> expr)
{
var memberExpr = expr.Body as MemberExpression;
if (memberExpr == null)
return;
var varName = memberExpr.Member.Name;
var varData = expr.Compile()();
// actual logging
...
}
Usage:
var test = "Foo";
Log(() => test);
Alternatively, if you're using C# 6.0, it can get a bit better using the nameof operator:
test.Log(nameof(test));
A better solution would be one that is leveraging the compiler abilities (specifically, the "Roslyn" compiler) and provide the member name on compile time.
Not really an answer, more of a pointer, but you could try doing something with your application that you're using(e.g. visual studio) instead of doing it in code. What I mean is make it rewrite everything that looks like [variable].Log(); to [variable].Log([variable])
I am pretty sure that there has to be some weird macro or plugin which does this for you before compiling.

Unable to get a string out of a method

I am really new to coding, never studied it or something similar, just learning it myself, never done it before, but I am trying to create my first real application right new.
However, I have some problems for 2 days which I just can't figure out, so I hope you can help me out.
Alright, so before the youtubedlCurrentWorker_Process() is created, I did define 'public string CurrentYouTubeDLVersion'.
How ever, when a button in my application executes the youtubedlCompareVersion_Process(), the CurrentYouTubeDLVersion string is empty, when it comes at the compare point.
Below is just a little part of my code.
Why is the string CurrentYouTubeDLVersion empty in the CompareVersion while the GetCurrentVersion ran before it?
Even if I double click "CurrentYouTubeDLVersion" in Visual Studio, it won't show a link to the one in the GetCurrentVersion_Process.
namespace MediaDownloader
{
public partial class updates : UserControl
{
public string LatestYoutubeDLVersion;
public string CurrentYouTubeDLVersion;
public void youtubedlGetCurrentVersion_Process()
{
if (File.Exists(YouTubeDLPath))
{
//Here I get the current version of youtube-dl.exe, to get the version number, we have to run youtube-dl.exe --version
Process youtubedl = new Process();
youtubedl.StartInfo.CreateNoWindow = true;
youtubedl.StartInfo.UseShellExecute = false;
youtubedl.StartInfo.RedirectStandardOutput = true;
youtubedl.StartInfo.RedirectStandardError = true;
youtubedl.StartInfo.FileName = YouTubeDLPath;
youtubedl.StartInfo.Arguments = " --version";
youtubedl.Start();
string CurrentYouTubeDLVersion = youtubedl.StandardOutput.ReadToEnd();
this.Dispatcher.Invoke((Action)(() =>
{
CurrentYouTubeDLVersionText.Text = "Current youtube-dl.exe version: " + CurrentYouTubeDLVersion;
YouTubeDLVersionStatusText.Text = null;
UpdateYouTubeDL.IsEnabled = false;
}));
}
public void youtubedlCompareVersion_Process()
{
youtubedlGetCurrentVersion_Process();
string LatestYoutubeDLVersion = WebClient.DownloadString("https://yt-dl.org/latest/version");
MessageBox.Show("Latest:" + LatestYoutubeDLVersion + "Current " + CurrentYouTubeDLVersion);
int YouTubeDLUptodate = CurrentYouTubeDLVersion.CompareTo(LatestYoutubeDLVersion);
if (YouTubeDLUptodate < 1)
{
YouTubeDLVersionStatusText.Text = "Your youtube-dl.exe is out of date, please click the button below to update.";
UpdateYouTubeDL.IsEnabled = true;
}
else
{
YouTubeDLVersionStatusText.Text = "youtube-dl.exe is up to date!";
UpdateYouTubeDL.IsEnabled = false;
}
}
}
Inside the youtubedlGetCurrentVersion_Process method, you're creating a new CurrentYouTubeDLVersion string, and it's completely separate from the public CurrentYouTubeDLVersion you added to the top of the class.
string CurrentYouTubeDLVersion = youtubedl.StandardOutput.ReadToEnd();
Assign to the class-level variable you made, instead of creating a new string:
CurrentYouTubeDLVersion = youtubedl.StandardOutput.ReadToEnd();
Then the value will be available to you in youtubedlCompareVersion_Process.
Take out the 'string' in front of CurrentYouTubeDLVersion and it should work
public youtubedlGetCurrentVersion_Process()
{
/* removed code to make easier to read */
//string CurrentYouTubeDLVersion = youtubedl.StandardOutput.ReadToEnd();
CurrentYouTubeDLVersion = youtubedl.StandardOutput.ReadToEnd();
/* removed code to make easier to read */
}

How do I pass a string to a function requiring an Object?

I am using Chello (the c# wrapper for the Trello API). I need to pass the argument "createCard" as per the documentation here: https://trello.com/docs/api/card/index.html
And this is the function I am using from Chello:
public IEnumerable<CardUpdateAction> ForCard(string cardId, object args)
{
string queryString = BuildQueryString(args);
return GetRequest<List<CardUpdateAction>>("/cards/{0}/actions?{1}", cardId, queryString);
}
I have tried calling this in this way:
List<CardUpdateAction> cua = chello.CardUpdates.ForCard("5264d37736695b2821001d7a","createCard").ToList();
but I get the error: Parameter Count Mismatch
on this function:
protected static string BuildQueryString(object args)
{
string queryString = String.Empty;
if (args != null)
{
StringBuilder sb = new StringBuilder();
foreach (var prop in args.GetType().GetProperties())
{
sb.AppendFormat("{0}={1}&", prop.Name, prop.GetValue(args, null));
}
if (sb.Length > 0) sb.Remove(sb.Length - 1, 1);
queryString = sb.ToString();
}
return queryString;
}
The problem is the fact that your API you are using expects you to pass in a class that has public properties equal to the tags you want to use.
This is very easy to do using Anonymous Types (I am doing a slightly different example to help illustrate a point)
//This will cause BuildQueryString to return "actions=createCard&action_fields=data,type,date"
var options = new { actions = "createCard", action_fields = "data,type,date" };
List<CardUpdateAction> cua = chello.CardUpdates.ForCard("5264d37736695b2821001d7a",options).ToList();
string is an object. Every type in .NET platform inherits from Object. This is called Unified Type System.
On the other hand, we have the Liskov Substitution Principle, which put simply, says that if B is a subtype of A (B is A), then you should be able to use B, wherever A is used.
Based on these reasons, you can pass string to any method that accepts an object as an argument.
You can test it:
public void DoSomething(object args)
{
}
public void Main()
{
DoSomething("some string argument, instead of the object");
}
It works just fine. No error.

Categories

Resources