Piping processes, VSLab Shell foundations

by cisterni 16. September 2008 09:40

VSLab provides the foundations for using Visual Studio as an interactive environment, a sort of big environment for Domain Specific Languages in which you can issue commands interactively to control virtually anything. Now that this foundation is in place we are working to provide the set of packages that shows this potential and transform VSLab into something that is usable for people, not only by F# programmers. Of course this is also a way to find the right abstractions you need when developing packages so that we can possibly extend the core to support new functionalities.

One of the coolest packages I'm working on is the VSLab Shell package, a mean to turn VS into next generation interactive shell, in which text and GUI mix together to obtain a new environment in which point-and-click is used in conjunction with text-based scripts to get the best from the two environments. We are currently defining the core operations of a traditional shell, such as process management and piping, and I want to share with you a neat piece of code we wrote this night to express process piping using F# and F# interactive. Here is the code:

open System.Diagnostics

open System.IO

let pwd = Directory.GetCurrentDirectory

let createProcess exec args =

  let pi = new ProcessStartInfo()

  pi.Arguments <- args

  pi.CreateNoWindow <- false

  pi.RedirectStandardInput <- true

  pi.RedirectStandardError <- true

  pi.RedirectStandardOutput <- true

  pi.UseShellExecute <- false

  pi.WorkingDirectory <- pwd()

  pi.FileName <- exec

  let p = new Process()

  p.StartInfo <- pi  p

let (^>) (out:Process) (pf: bool -> string -> unit) =

  out.ErrorDataReceived.Add(fun d -> pf false d.Data)

  out.OutputDataReceived.Add(fun d -> pf true d.Data)

  out.Start()

  out.BeginErrorReadLine()

  out.BeginOutputReadLine()

  out

let (^|) (inp:Process) (out:Process) =

  inp.EnableRaisingEvents <- true

  inp.ErrorDataReceived.Add(fun d -> out.StandardInput.WriteLine d.Data)

  inp.OutputDataReceived.Add(fun d -> out.StandardInput.WriteLine d.Data)

  inp.Exited.Add(fun _ -> out.StandardInput.Close())

  inp.Start()

  inp.BeginErrorReadLine()

  inp.BeginOutputReadLine()

  inp

let p = createProcess @"c:\Windows\System32\Robocopy.exe" "/?"

let s = createProcess @"c:\Windows\System32\sort.exe" ""

let q = createProcess @"c:\Windows\System32\sort.exe" "/R"

p ^| q ^| s ^> (fun out d -> printfn "%s: %s" (if out then "out" else "err") d)

s.WaitForExit()

This code shows a limit of .NET API, we have to coordinate the piping process explicitly because we cannot specify the handle to the streams in the ProcessStartInfo structure (in Win32 would be possible). We define two overloaded operators using the ^ prefix to have right association. The callbacks provided by the Process class allow to avoid polling of streams. This code has requested an amount of spelunking of System.IO namespace, I was surprised to find that using the general Stream class it is not possible to multiplex IO operations as the select system call would have permitted. Some day I will post considerations about this design that at a first sight may seem weird but after some thoughts I decided that is reasonable though far from elegant.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

General | Shell

Powered by BlogEngine.NET 1.4.0.0
Theme by Mads Kristensen

VSLab blog

VSLab is a Visual Studio extension designed to support Visual Studio interaction from F# interactive. It is a Microsoft product developed at University of Pisa, by a team lead by Antonio Cisternino.

Resources

Recent comments

Comment RSS

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in  anyway.

© Copyright 2008