Cake build: access original command line arguments string? - c#

While trying to squeeze Mono.Options into my Cake script, I noticed I wasn't entirely sure how to give it the original arguments string from the command line call that launched the Cake script in the first place. Mono.Options Parse method takes what would be a typical console app's string[] args parameter, so I need to feed it something it can work with.
I know I can query the context for specific arguments with ArgumentAlias calls, but is there any way to access the entire original string calling string?

Cake scripts are essentially just a regular .NET Process to you can access it through System.Environment.GetCommandLineArgs()
Example PoC
Quick n dirty example of one way you could use Mono.Options with Cake below
#addin nuget:?package=Mono.Options&version=5.3.0.1
using Mono.Options;
public static class MyOptions
{
public static bool ShouldShowHelp { get; set; } = false;
public static List<string> Names { get; set; } = new List<string>();
public static int Repeat { get; set; } = 1;
}
var p = new OptionSet {
{ "name=", "the name of someone to greet.", n => MyOptions.Names.Add (n) },
{ "repeat=", "the number of times to MyOptions.Repeat the greeting.", (int r) => MyOptions.Repeat = r },
// help is reserved cake command so using options instead
{ "options", "show this message and exit", h => MyOptions.ShouldShowHelp = h != null },
};
try {
p.Parse (
System.Environment.GetCommandLineArgs()
// Skip Cake.exe and potential cake file.
// i.e. "cake --name="Mattias""
// or "cake build.cake --name="Mattias""
.SkipWhile(arg=>arg.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)||arg.EndsWith(".cake", StringComparison.OrdinalIgnoreCase))
.ToArray()
);
}
catch (OptionException e) {
Information("Options Sample: ");
Information (e.Message);
Information ("--options' for more information.");
return;
}
if (MyOptions.ShouldShowHelp || MyOptions.Names.Count == 0)
{
var sw = new StringWriter();
p.WriteOptionDescriptions (sw);
Information(
"Usage: [OPTIONS]"
);
Information(sw);
return;
}
string message = "Hello {0}!";
foreach (string name in MyOptions.Names) {
for (int i = 0; i < MyOptions.Repeat; ++i)
Information (message, name);
}
Example output
cake .\Mono.Options.cake will output help as no names specified
cake .\Mono.Options.cake --options will output "help"
cake .\Mono.Options.cake --name=Mattias will greet me
cake .\Mono.Options.cake --name="Mattias" --repeat=5 will greet me 5 times
cake .\Mono.Options.cake --name="Mattias" --repeat=sdss will fail and report because repeat not a number

Related

How to get controller name in stacktrace

I have an ASP.NET MVC application and would like to log the names of all the involved methods, for certain user flows on the website.
The following code does that, but it stops at the root/first triggered method:
public string GetStackTraceMethods()
{
List<string> stackTraceMethods = new List<string>();
StackTrace stackTrace = new StackTrace();
string methodName = string.Empty;
// These are the default/framework methods that I do not want to log. So, excluding them.
List<string> methodNamesToIgnore = new List<string> {
"GetStackTraceMethods", "MoveNext", "Start",
"lambda_method", "BuildProduct", "BuildProducts",
"b__2", ".ctor", "ToList"
};
foreach (StackFrame frame in stackTrace.GetFrames())
{
methodName = frame.GetMethod().Name;
if (methodName == "InvokeActionMethod")
{
break;
}
else if (methodNamesToIgnore.Any(x => x == methodName))
{
continue;
}
else
{
stackTraceMethods.Add(frame.GetMethod().Name);
}
}
return string.Join(" < ", stackTraceMethods);
}
This returns a result like this:
Some_Service_Method_2 < Some_Service_Method_1 < Controller_Method
Is it possible to get the controller's name also from the stack trace, so that I can include that in the list?
UPDATE: the GetStackTraceMethods() method is placed in the bottom level of the flow. i.e it is placed in the same class as Some_Service_Method_2
#Chetan's comment worked. It gives the class name of that method.

Ban a variable from a list with a "ban" list

How can I ban a variable from a list without removing it from that list by adding the variable to a list of "banned" variable?
I wish to be able to type in a string. That string is compared to the file names in a folder. If there is a match, the file is read. If I type this same string again, the file should not be read again. There for I want to have a list of "banned" string that is checked whilst typing to avoid the file to be read again.
I have tried a few ways but not getting there. Below is an example of my last attempt.
What would be the best way?
public class test
{
string scl= "test3";
List <string> lsf,lso;
void Start ()
{
lsf=//file names
new List<string>();
lso=//files open
new List<string>();
lsf.Add("test0");
lsf.Add("test1");
lsf.Add("test2");
lsf.Add("test3");
lsf.Add("test4");
lso.Add("idhtk49fngo");//random string
}
void Update ()
{
if
(
Input.GetKeyDown("a")
)
{
for
(
int i=0;
i<lsf.Count;
i++
)
{
if(lsf[i]==scl)
{
Debug.Log
(i+" is read");
for
(
int j=0;
j<lso.Count;
j++
)
{
//how can i avoid reading
//lsf[3] here the second time
//"a" is pressed (by having "test3"
//added to a "ban" list (lso) )
if(scl!=lso[j])
{
lso.Add(lsf[i]);
}
}
}
}
}
}
Michael’s answer is the way to go here but it can be improved using the more appropriate collection available to keep track of opened files; if you want uniqueness use a set, not a list:
HashSet<string> openedFiles = new HashSet<string>();
public static bool TryFirstRead(
string path,
out string result)
{
if (openedFiles.Add(path))
{
result = File.ReadAllText(path);
return true;
}
result = null;
return false;
}
Also, I’d avoid throwing vexing exceptions. Give the consumer a friendly way to know if the file was read or not, don’t make them end up having to use exceptions as a flow control mechanism.
I didn't understand although if you want to replace a value from another list.
You can use the list index to create a new list with the values which you removed.
String list1 = {"hi", "hello", "World"};
String list2 = {"bye", "goodbye", "World"};
List1[1] = list2[1];
I would suggest such way:
public static List<string> openedFiles = new List<string>();
public static string ReadFileAndAddToOpenedList(string path)
{
if (openedFiles.Contains(path))
throw new Exception("File already opened");
// Instead of throwing exception you could for example just log this or do something else, like:
// Consolle.WriteLine("File already opened");
else
{
openedFiles.Add(path);
return File.ReadAllText(path);
}
}
The idea is - on every file read, add file to list, so you can check every time you try read file, if it was already read (or opened). If it is, throw exception (or do something else). Else read a file.
You could instead of making it a string list use your own class
public class MyFile
{
public string Name;
public bool isOpen;
public MyFile(string name)
{
Name = name;
isOpen = false;
}
}
List<MyFile> lsf = new List<MyFile>()
{
new MyFile("test0"),
new MyFile("test1"),
new MyFile("test2"),
new MyFile("test3"),
new MyFile("test4")
};
Than when you read the file set isOpen to true
MyFile[someIndex].isOpen = true;
and later you can check this
// E.g. skip in a loop
if(MyFile[someIndex]) continue;
You could than also use Linq in order to get a list of only unread files:
var unreadFiles = lsf.Select(f => f.Name).Where(file => !file.isOpen);

How to implement custom command line & execution

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.

Run multiple script in SAP GUI

Context : I have two open sessions in my SAP GUI with following id :
/app/con[0]/ses[0]
/app/con[0]/ses[1]
I want to run 1 script (vbs) in each session, this is my code :
foreach (GuiSession s in _dicSap[tmpDKey].get_lstSapSession())
{
if (!s.Busy)
{
Process p = Process.Start(scriptName, s.Id); //s.Id=/app/con[0]/ses[0]
await Task.Delay(5000);
break;
}
}
Problem : my scripts are correctly executed but they are execute one by one.
I want to run those scripts in simultaneously. I don't understand because I haven't use .WaitForExit().
Is it my code which is wrong or is it impossible to run multi-script in SAP GUI in C# ?
Sorry for my english.
Regards
might be necro-threading, but this is how I solved a similar problem.
In my case I had to run N tasks, not two. Please also note that I had the script steps in c# code, not in separate files. Anyhow, this solution should fit your requirements.
First of all, you'll need to create multiple sessions of an existing (initial) session:
for (int i = 0; i < numOfSessions - 1 ; i++)
{
SapSession.CreateSession();
}
All these sessions will be placed in a list (sessionList). I use a custom sessionDetails class because I need to store IDs and activity information:
for (int i = 0; i < _maxSessions; i++)
{
sessionDetail sd = new sessionDetail((GuiSession)sapConnection.Sessions.Item(i), false, i);
sessionList.Add(sd);
}
class sessionDetail
{
public GuiSession sapSession { get; }
public bool isUsed { get; set; }
public int sessionId { get; set; }
public sessionDetail(GuiSession SapSession, bool IsUsed, int SessionId)
{
sapSession = SapSession;
isUsed = IsUsed;
sessionId = SessionId;
}
}
Secondly you'll need to parallelize execution of your program.
Let’s assume you’ve got an array of scripts scr that you need to execute:
string[] scr = { "scriptingTask1", " scriptingTask2", " scriptingTask3" };
Then you’ll create parallel threads for each script:
Parallel.ForEach<string>(scr
, new ParallelOptions { MaxDegreeOfParallelism = _maxSessions }
, (script) => DoSomeWork(script, sessionList)
);
The method that you pass for a lambda will assign the scripting Tasks to sessions and launch them
private void DoSomeWork(string scrptTask, List<sessionDetail> _sessionList)
{
sessionDetail _sessionToUse;
foreach (sessionDetail s in _sessionList)
{
if (!s.isUsed)
{
_sessionToUse = s;
s.isUsed = true;
//// Do your stuff here
s.isUsed = false;
break;
}
}
}
Fourth, make sure that addresses in your scripts (like "/app/con[0]/ses[0]/wnd[0]/usr/ctxtP_EKORG”) use corresponding session IDs in them. You can see it in the middle of this path (ses[0]).
If you keep referencing ses[0] in all the scripts, you'll likely get "element wasn't found by ID" errors.
Constantine.

"Access to path [...] is denied" even when I Run as Administrator

My program is not liking my File.Copy, as it says
Access to the path 'C:\Users\Me\Documents\Visual Studio
2013\Projects\Unfriendly\Unfriendly\bin\Debug\Data' is denied
when I try to invoke my function
public void AddFriendList ( string newDataPath )
{
// dataPath: Full file path to Facebook data file being added
string newFriendsPath = System.IO.Path.Combine(new string [] { newDataPath,"html","friends.htm"});
using ( StreamReader sr = new StreamReader(this._datadir) )
{
Match friendsULs = DataReader._lregx.Match(sr.ReadToEnd());
if ( !friendsULs.Success )
{
throw new Exception(String.Format("Couldn't find exactly one piece of HTML matching regex {0}",
DataReader._lregx.ToString()));
}
Dictionary<string, int> friendMap = new Dictionary<string, int>();
foreach ( Match thisFriendRegex in DataReader._fregx.Matches(friendsULs.ToString()) )
{
var thisFriendString = thisFriendRegex.ToString();
if ( friendMap.ContainsKey(thisFriendString) )
{
++friendMap[thisFriendString];
}
else
{
friendMap[thisFriendString] = 1;
}
_fhist.Add(new FriendList { friendNames = friendMap, timestamp = File.GetCreationTime(newFriendsPath) });
}
}
// Copy new data Facebook data into repository
File.Copy(newDataPath, this._datadir);
}
with newDataPath being C:\Users\Me\Desktop\facebook_data. I've tried Run as Administrator and still get the error.
The full meat of my project can be seen at https://github.com/jamkin/Unfriendly/blob/master/Unfriendly/DataReader.cs and, by the way, it looks like GitHub doesn't know what to do with #"..." strings since it's making everything blue after
private static readonly string _instructsBody = String.Format(#"AddFriendList <FacebookDataPath>: Adds to reposistory the instance of Facebook data found at FacebookDataPath\n
Example usage: AddFriendList C:\Users\Jamin\Desktop\facebook_data_1\n
{0}\n
PrintInstructions: Prints instructions (like you just did!)\n
{0}\n
CompareLastTwo: Shows difference between latest two friends lists in repository",
new String('-', DataReader._instructsHeader.Length));
Is there a way to write that and not confuse GitHub?

Categories

Resources