Introduction
Have you ever wanted to run a .linq script on the power shell or command line and go all functional? Linqoott is a console LINQ execution engine. It reads .linq files and dumps the output to the console.
Using the Code
First we get the script source from the first argument:
var isLinq = args[0].ToLower().EndsWith(".linq");
Find out the LINQ "kind"... (statement/expression)
string linqType = (isLinq)?XDocument.Parse(lins[0]).Root.Attribute("Kind").Value:string.Empty;
Is it a single expression or a statement (multiple lines), this effects how you format the script into a code block.
var script = string.Format(("Statements" == linqType) ? "{0}\nreturn null;" : "return {0};",
string.Join("\n", (isLinq) ? lins.Skip(2).Where(s => !s.TrimStart().StartsWith("//")) : lins));
Have a script, must execute. Not as hard as you think, embed the script in a template C# Main()
. It looks like this:
static readonly string[] tmplApp = new string[]{
"using System;",
"using System.Collections.Generic;",
"using System.Linq;",
"using System.Xml.Linq;",
"using System.Text;",
"using System.IO;",
"",
"class Script{ public static object Main(string[] args){{CODE}} }",
"",
"public static class ScriptExtensions",
"{",
" public static object Dump(this object value)",
" {",
" if (null != value)",
" {",
" if (value is IEnumerable<object>)",
" Console.WriteLine(string.Join(\"\\n\", (value as IEnumerable<object>).Select(s => s.Dump().ToString())));",
" else",
" Console.WriteLine(value);",
" }",
" return value;",
" }",
" public static object ToFile(this object value, string fileName) { File.WriteAllText(fileName, value.Dump()); return value; }",
"}"};
The "Dump
" method embedded in there is to help output the values in statements.
Next we "compiled" the source. I can't claim credit here see the Code Project Article: Dynamic Code Integration.
private static Assembly CompileSource(string sourceCode)
{
CodeDomProvider cpd = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters() { GenerateExecutable = false };
cp.ReferencedAssemblies.AddRange(new string[] { "System.dll", "System.Xml.dll", "System.Xml.Linq.dll", "System.Core.dll", "System.Data.dll" });
CompilerResults cr = cpd.CompileAssemblyFromSource(cp, sourceCode);
return cr.CompiledAssembly;
}
So we have this vague "Assembly" object thingy... What do we do with it? This is a good time to reflect. NO! not on your coding skills, System.Reflection
, you hack monkey!
var scrObj = asm.CreateInstance("Script");
try{
object result = scrObj.GetType().InvokeMember("Main", BindingFlags.InvokeMethod, null,scrObj, new object[] { args.Skip(1).ToArray() });
if ("Statements" != linqType)
result.Dump();
}
catch (Exception x)
{
Console.WriteLine("Failed executing script:\n\n{0}\n\n{1}", script, x.Message);
}
And that's it! You can run a script...
Samples Worth a Thousand Bugs
Here's a LINQ script sample. This enumerates your 'C:\program files' directory and returns all .exe's with a file major version greater than 10, and sorts by minor version. Silly example, but it exercises the code.
Directory.GetFiles(@"c:\program files", "*.exe", SearchOption.AllDirectories)
.Where(v=>v.FileMajorPart>10).OrderBy(v=>v.FileMinorPart)
.Select(f=>f.FileName.Replace(@"c:\program files", args[0]))
.ToArray().Select(f=>FileVersionInfo.GetVersionInfo(f))
Let's execute that ... On my machine it returns:
c:\foo\Common Files\logishrd\WUApp64.exe
c:\foo\Common Files\Microsoft Shared\DW\DW20.EXE
c:\foo\Common Files\Microsoft Shared\DW\DWTRIG20.EXE
c:\foo\Common Files\Microsoft Shared\OFFICE14\MSOICONS.EXE
c:\foo\Common Files\Microsoft Shared\OFFICE14\MSOXMLED.EXE
...
Wait a minute?! "c:\foo\..." where did that come from? Hmm, I added arguments. See this:
.Select(f=>f.FileName.Replace(@"c:\program files", args[0]))
That replaces "c:\program files" with the first passed command-line argument, in this case "c:\foo" (see sample project's debug settings). Pretty cool
As always, code safe!