Skip to content

Commit 69a2525

Browse files
brettfoKevinRansom
authored andcommitted
move console input/output redirect out of script helper (#8108)
* move console input/output redirect out of script helper * address review feedback * string splitting is hard
1 parent cda5e31 commit 69a2525

File tree

6 files changed

+76
-81
lines changed

6 files changed

+76
-81
lines changed

src/fsharp/FSharp.Compiler.Private.Scripting/FSharp.Compiler.Private.Scripting.fsproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<Compile Include="TextHelpers.fs" />
1312
<Compile Include="FSharpScript.fs" />
1413
</ItemGroup>
1514

1615
<ItemGroup>
17-
<NuspecProperty Include="FSharpCoreVersion=$(FSCorePackageVersion)-$(VersionSuffix)"/>
18-
<NuspecProperty Include="TargetFramework=$(TargetFramework)"/>
16+
<NuspecProperty Include="FSharpCoreVersion=$(FSCorePackageVersion)-$(VersionSuffix)" />
17+
<NuspecProperty Include="TargetFramework=$(TargetFramework)" />
1918
</ItemGroup>
2019

2120
<ItemGroup>

src/fsharp/FSharp.Compiler.Private.Scripting/FSharpScript.fs

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,22 @@ open System.Threading
77
open FSharp.Compiler
88
open FSharp.Compiler.Interactive.Shell
99

10-
type FSharpScript(?captureInput: bool, ?captureOutput: bool, ?additionalArgs: string[]) as this =
11-
let outputProduced = Event<string>()
12-
let errorProduced = Event<string>()
10+
type FSharpScript(?additionalArgs: string[]) =
1311

14-
// handle stdin/stdout
15-
let stdin = new CapturedTextReader()
16-
let stdout = new EventedTextWriter()
17-
let stderr = new EventedTextWriter()
18-
do stdout.LineWritten.Add outputProduced.Trigger
19-
do stderr.LineWritten.Add errorProduced.Trigger
20-
let captureInput = defaultArg captureInput false
21-
let captureOutput = defaultArg captureOutput false
2212
let additionalArgs = defaultArg additionalArgs [||]
23-
let savedInput = Console.In
24-
let savedOutput = Console.Out
25-
let savedError = Console.Error
26-
do (fun () ->
27-
if captureInput then
28-
Console.SetIn(stdin)
29-
if captureOutput then
30-
Console.SetOut(stdout)
31-
Console.SetError(stderr)
32-
())()
33-
3413
let config = FsiEvaluationSession.GetDefaultConfiguration()
3514
let computedProfile =
3615
// If we are being executed on the desktop framework (we can tell because the assembly containing int is mscorlib) then profile must be mscorlib otherwise use netcore
3716
if typeof<int>.Assembly.GetName().Name = "mscorlib" then "mscorlib"
3817
else "netcore"
39-
let baseArgs = [| this.GetType().Assembly.Location; "--noninteractive"; "--targetprofile:" + computedProfile; "--quiet" |]
18+
let baseArgs = [| typeof<FSharpScript>.Assembly.Location; "--noninteractive"; "--targetprofile:" + computedProfile; "--quiet" |]
4019
let argv = Array.append baseArgs additionalArgs
4120
let fsi = FsiEvaluationSession.Create (config, argv, stdin, stdout, stderr, collectible=true)
4221

4322
member __.AssemblyReferenceAdded = fsi.AssemblyReferenceAdded
4423

4524
member __.ValueBound = fsi.ValueBound
4625

47-
member __.ProvideInput = stdin.ProvideInput
48-
49-
member __.OutputProduced = outputProduced.Publish
50-
51-
member __.ErrorProduced = errorProduced.Publish
52-
5326
member __.Fsi = fsi
5427

5528
member __.Eval(code: string, ?cancellationToken: CancellationToken) =
@@ -75,12 +48,4 @@ type FSharpScript(?captureInput: bool, ?captureOutput: bool, ?additionalArgs: st
7548

7649
interface IDisposable with
7750
member __.Dispose() =
78-
if captureInput then
79-
Console.SetIn(savedInput)
80-
if captureOutput then
81-
Console.SetOut(savedOutput)
82-
Console.SetError(savedError)
83-
stdin.Dispose()
84-
stdout.Dispose()
85-
stderr.Dispose()
8651
(fsi :> IDisposable).Dispose()

src/fsharp/FSharp.Compiler.Private.Scripting/TextHelpers.fs

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
namespace FSharp.Compiler.Scripting.UnitTests
2+
3+
open System
4+
open System.Collections.Generic
5+
open System.IO
6+
open System.Text
7+
8+
type internal CapturedTextReader() =
9+
inherit TextReader()
10+
let queue = Queue<char>()
11+
member _.ProvideInput(text: string) =
12+
for c in text.ToCharArray() do
13+
queue.Enqueue(c)
14+
override _.Peek() =
15+
if queue.Count > 0 then queue.Peek() |> int else -1
16+
override _.Read() =
17+
if queue.Count > 0 then queue.Dequeue() |> int else -1
18+
19+
type internal RedirectConsoleInput() =
20+
let oldStdIn = Console.In
21+
let newStdIn = new CapturedTextReader()
22+
do Console.SetIn(newStdIn)
23+
member _.ProvideInput(text: string) =
24+
newStdIn.ProvideInput(text)
25+
interface IDisposable with
26+
member __.Dispose() =
27+
Console.SetIn(oldStdIn)
28+
newStdIn.Dispose()
29+
30+
type internal EventedTextWriter() =
31+
inherit TextWriter()
32+
let sb = StringBuilder()
33+
let lineWritten = Event<string>()
34+
member _.LineWritten = lineWritten.Publish
35+
override _.Encoding = Encoding.UTF8
36+
override _.Write(c: char) =
37+
if c = '\n' then
38+
let line =
39+
let v = sb.ToString()
40+
if v.EndsWith("\r") then v.Substring(0, v.Length - 1)
41+
else v
42+
sb.Clear() |> ignore
43+
lineWritten.Trigger(line)
44+
else sb.Append(c) |> ignore
45+
46+
type internal RedirectConsoleOutput() =
47+
let outputProduced = Event<string>()
48+
let errorProduced = Event<string>()
49+
let oldStdOut = Console.Out
50+
let oldStdErr = Console.Error
51+
let newStdOut = new EventedTextWriter()
52+
let newStdErr = new EventedTextWriter()
53+
do newStdOut.LineWritten.Add outputProduced.Trigger
54+
do newStdErr.LineWritten.Add errorProduced.Trigger
55+
do Console.SetOut(newStdOut)
56+
do Console.SetError(newStdErr)
57+
member _.OutputProduced = outputProduced.Publish
58+
member _.ErrorProduced = errorProduced.Publish
59+
interface IDisposable with
60+
member __.Dispose() =
61+
Console.SetOut(oldStdOut)
62+
Console.SetError(oldStdErr)
63+
newStdOut.Dispose()
64+
newStdErr.Dispose()

tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14+
<Compile Include="ConsoleHelpers.fs" />
1415
<Compile Include="TestHelpers.fs" />
1516
<Compile Include="FSharpScriptTests.fs" />
1617
<Compile Include="CompletionTests.fs" />

tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,22 @@ type InteractiveTests() =
3232

3333
[<Test>]
3434
member __.``Capture console input``() =
35-
use script = new FSharpScript(captureInput=true)
36-
script.ProvideInput "stdin:1234\r\n"
35+
use input = new RedirectConsoleInput()
36+
use script = new FSharpScript()
37+
input.ProvideInput "stdin:1234\r\n"
3738
let opt = script.Eval("System.Console.ReadLine()") |> getValue
3839
let value = opt.Value
3940
Assert.AreEqual(typeof<string>, value.ReflectionType)
4041
Assert.AreEqual("stdin:1234", value.ReflectionValue)
4142

4243
[<Test>]
4344
member __.``Capture console output/error``() =
44-
use script = new FSharpScript(captureOutput=true)
45+
use output = new RedirectConsoleOutput()
46+
use script = new FSharpScript()
4547
use sawOutputSentinel = new ManualResetEvent(false)
4648
use sawErrorSentinel = new ManualResetEvent(false)
47-
script.OutputProduced.Add (fun line -> if line = "stdout:1234" then sawOutputSentinel.Set() |> ignore)
48-
script.ErrorProduced.Add (fun line -> if line = "stderr:5678" then sawErrorSentinel.Set() |> ignore)
49+
output.OutputProduced.Add (fun line -> if line = "stdout:1234" then sawOutputSentinel.Set() |> ignore)
50+
output.ErrorProduced.Add (fun line -> if line = "stderr:5678" then sawErrorSentinel.Set() |> ignore)
4951
script.Eval("printfn \"stdout:1234\"; eprintfn \"stderr:5678\"") |> ignoreValue
5052
Assert.True(sawOutputSentinel.WaitOne(TimeSpan.FromSeconds(5.0)), "Expected to see output sentinel value written")
5153
Assert.True(sawErrorSentinel.WaitOne(TimeSpan.FromSeconds(5.0)), "Expected to see error sentinel value written")

0 commit comments

Comments
 (0)