14

Can I pipe an OutputStream object created by OpenWrite (or similar) to a notebook instead of a file? I am aware that I can use NotebookWrite, but I want to have all the Write statements in my code to be redirected to file/notebook by simply switching a variable.

István Zachar
  • 47,032
  • 20
  • 143
  • 291

2 Answers2

16

I am not sure that this is possible. The $Output and $Messages variables hold the output stream to where the standard output (and the message output) from the kernel goes. If you check these, you'll see that they're simply set to stdout.

If you remove ReadProtected from NotebookWrite, you'll see that it is passing data to the front end instead of writing to an output stream.

All this suggest that it's not possible to redirect an output stream to an arbitrary notebook.


Instead of using output streams to switch the output "device", I'd suggest switching the output function. You could have a function write which can be set to write = Write[outputChannel, #]& or to write = NotebookWrite[nb, #]&.

If you already have a lot of code using Write then it would be inconvenient to rewrite it to use an alternative write function. But you can temporarily redefine Write using a block:

Block[{ Write = NotebookWrite[nb, #2]& },

 .... (* code called here *)

]

If you need to temporarily redefine Write with something that itself uses Write, then you can use the trick described here to "wrap" it with extra code.


Note: As @Heike said below, NotebookWrite[nb, Cell[BoxData[ToBoxes[#2]], "Output"]] & is better for writing into notebooks than the simple NotebookWrite[nb, #2]& I used above.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
  • 2
    +1 for using block, although you would probably need to define Write = NotebookWrite[#1, Cell[BoxData[ToBoxes[#2]], "Output"]] & or something to make it work. – Heike Jan 31 '12 at 13:42
  • I was afraid of this. Isn't the purpose of streaming/piping to provide means of saving data by easy redirection? Considering, that this bug is still not resolved, piping is really not that easy in Mathematica... – István Zachar Jan 31 '12 at 13:54
  • 2
    @Istvan I agree with Szabolcs. Streams are a pretty low-level I/O tool, suitable also for reading/writing of unstructured data (e.g., not even representing valid Mathematica expressions). Notebooks are structured documents, and writing to them assumes, to my mind, higher-level I/O abstraction. If you feel a need to redirect there, it might mean that your current design is too low-level (regarding logging or whatever purpose it is you use Write for), so you will be better off by redesigning this part of your code to use higher-level constructs. – Leonid Shifrin Jan 31 '12 at 15:10
  • @IstvánZachar, I did not realize that that bug was that old. – rcollyer Jan 31 '12 at 16:00
  • @István "bump" doesn't really work on a newsgroup as the old message may very well be lost in most newsreaders. Funny the moderator even let it through .. – Szabolcs Jan 31 '12 at 16:03
  • @LeonidShifrin: I thought that using streams is much fastar to save data than using other functions - at least for files. Now for notebooks, I understand that this might not be the case. – István Zachar Jan 31 '12 at 16:05
  • @Istvan The speed should never be the primary concern at the start, at least unless you know in advance that speed is central to the application. In Mathematica, we perhaps need to think of it more than in some other languages (e.g. Python), but the principle still holds. With my demonstrated attachement to microbenchmarks, I may sound hypocritical here, but I actually do follow this principle for whatever code I have to write. Microbenchmarking is useful to see how fast you can be within the language, if you absolutely have to (i.e. fore real bottlenecks). – Leonid Shifrin Jan 31 '12 at 17:58
  • @Szabolcs: Yes, I had plenty of time to figure that out and draw the consequences :) – István Zachar Jan 31 '12 at 18:42
6

Here's a way that allows you to actually redirect output to a notebook by using the newish DefineStreamOutputMethod function (which dates to 9.0). It's a bit hacky by its very nature, and the version I have here creates a new notebook for output, but I think it's a reasonable place to start. There's certainly room for refinement!

DefineOutputStreamMethod["Notebook",
 {
  "ConstructorFunction" ->
   Function[{name, isAppend, caller, opts},
    With[{state = Unique["NotebookOutputStream"]},
     state["notebook"] =
      CreateNotebook[FilterRules[opts, Options[Notebook]]];
     {True, state}]],

  "CloseFunction" -> Function[state, ClearAll[state]],

  "WriteFunction" -> Function[{state, bytes},
    (* Since we're writing to a cell, we don't want that trailing newline. *)
    With[{out = bytes /. {most___, 10} :> FromCharacterCode[{most}]},
     With[{nb = state["notebook"]},
      If[NotebookInformation@nb === $Failed || out === "",
       {0, state},
       NotebookWrite[nb, Cell[out, "Output"], After];
       {Length@bytes, state}]]]]
  }];

Try it out like this:

With[{stream = OpenWrite[Method -> "Notebook"]},
 Block[{$Output = stream},
  Do[Print[i], {i, 10}];
  Close[stream]]] 
Pillsy
  • 18,498
  • 2
  • 46
  • 92