2

Following this post, I want to use code to generate an input cell with new code. However, unlike the linked post, I want my code to dynamically determine the new code to be printed in the input cell.

The use of Defer is making this difficult. For example, the following works:

makeImportCell[filename_] := 
   CellPrint[ExpressionCell[
       Defer[data = Import[filename]], "Input"]]

makeImportCell["myData.txt"]

Output (as an input cell):

data = Import["myData.txt"]

But if I want the function to manipulate the string filename, it breaks:

makeImportCell[filename_] := Module[{file},
  file = filename <> ".txt"; 
  CellPrint[ExpressionCell[Defer[data = Import[file]], "Input"]]]

makeImportCell["myData"]

Output (as an input cell):

data = Import[file$45844993]

I'd also like to use If statements based on some other variables to alter the code that gets generated, but if I put any Ifs into Defer, they'll be left unevaluated. Maybe the best solution would be to have the function construct a large string containing exactly the code I want to print, and then print this string as input code rather than a string. But I can't figure out how to do this.


I just realized I could use

makeImportCell[filename_] := 
   CellPrint[ExpressionCell[
       Defer[data = Import[filename <> ".txt"]], "Input"]]

to accomplish the same thing the second function is trying to do. But this doesn't generalize. I want to be able to manipulate the code (and strings within the code) that is to be generated, inside the body of Module, and then CellPrint exactly the code I want.


EDIT

J.M.'s answer prompted me to ask a more general version of this question, and the answer given there is particularly simple and elegant. It solves the problem of converting any arbitrary string of code into an input cell.

WillG
  • 960
  • 4
  • 14

2 Answers2

6

You should just use With instead of Module, as Carl Woll suggests in the comments:

makeImportCell[filename_] := With[{file = filename <> ".txt"},
  CellPrint[ExpressionCell[Defer[data = Import[file]], "Input"]]]

However, be aware that usage of CellPrint for producing "Input" cells is discouraged:

I'm not a huge fan of this because of the combination of CellPrint and the "Input" style. This creates Input cells which have GeneratedCell->True and CellAutoOverwrite->True (this is documented behavior of CellPrint). If you start using these cells for real, then you may have unpleasant side effects...like your Input cells disappearing when you evaluate inputs above them. If you really want Input cells, I'd recommend a NotebookWrite based solution. The equivalent formulation would be NotebookWrite[EvaluationNotebook[],Cell[BoxData[ToString[#, InputForm]], "Input"]] &. – John Fultz (Jul 9, 2017 at 5:19)

To generate "Input" cells that behave exactly like regular user-created cells, use NotebookWrite instead:

makeImportCell[filename_] := With[{file = filename <> ".txt"},
  NotebookWrite[EvaluationNotebook[], 
   Cell[BoxData@MakeBoxes[data = Import[file]], "Input"]]]

Further reading:

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
  • 2
    If the GeneratedCell and CellAutoOverwrite options are the problem, you can also just manually set those options: CellPrint[ExpressionCell[expr, "Input", GeneratedCell -> False, CellAutoOverwrite -> False]] – Sjoerd Smit Jul 21 '22 at 06:33
  • 1
    @SjoerdSmit I tried it. My experiments showed that it doesn't allow to reproduce the behavior we get with NotebookWrite. I don't know what, but there seems to be something besides these options that affects this. See the last words in this comment. Also, I think John Fultz has serious reasons to recommend NotebookWrite instead of CellPrint. – Alexey Popkov Jul 21 '22 at 08:36
  • I also want the generated cell to be an initialization cell, which I can do with CellPrint[..., InitializationCell -> True], but not NotebookWrite. However, I'm not finding any overwrite issues with CellPrint. Maybe the default behavior was updated? – WillG Jul 21 '22 at 19:41
  • E.g., execute the following code multiple times. I get a new generated cell each time, and nothing is overwritten. CellPrint[ Cell[BoxData@"Module[{x}, x = 1]", "Input", InitializationCell -> True]]. – WillG Jul 21 '22 at 19:42
  • @WillG Please evaluate any but the last of generated cells to see the effect. – Alexey Popkov Jul 21 '22 at 19:58
  • It still seems to work fine. Even when I evaluate any of the generated cells, everything seems normal. ?? E.g., evaluate the line of code in the most recent comment above three times, then execute the middle cell of the generated outputs. – WillG Jul 21 '22 at 20:08
  • 1
    @WillG Just tried, yes: with such simple setup it works as expected, but not with more complicated. Try the setup from the linked question: CellPrint/@{Cell["1\n2","Input",InitializationCell->True],Cell["I'll disappear after evaluation of the previous cell!","Text"],Cell["3","Input",InitializationCell->True]};. – Alexey Popkov Jul 22 '22 at 01:19
4

This required a bit more trickery than I would like:

makeImportCell[filename_] := Module[{file},
    file = filename <> ".txt";
    CellPrint[Cell[BoxData[RowBox[{"data", "=", 
                                   RowBox[{"Import", "[",
                                           "\"" <> file <> "\"", "]"}]}]], 
                   "Input"]]]

and then evaluating makeImportCell["fooba"] does print a cell with data = Import["fooba.txt"].

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
  • Thanks, it seems like I can use a similar method to construct arbitrary output code. However, I'm not familiar with the BoxData and RowBox structure that is apparently used to encode notebook expressions. Is there a handy way to convert an already-typed notebook expression, e.g., data=Import["filename.txt"], into the explicit BoxData/RowBox form you used here? – WillG Jul 20 '22 at 21:24
  • 1
    Something like MakeBoxes[data = Import["filename.txt"], StandardForm] would be useful for that purpose. – J. M.'s missing motivation Jul 20 '22 at 21:30
  • I might make a new post about this, but... Suppose I have some string str which is a valid piece of code that evaluates upon ToExpression@str. I can simply copy/paste the contents of this string into a new cell, and they are automatically formatted correctly. Therefore, it seems this copy/pasting action should be easy to automate with code. Is there a way to use CellPrint so that str appears in a new cell as correctly formatted code? – WillG Jul 21 '22 at 05:33
  • 1
    I like the method outlined above with MakeBoxes, but it still requires manually fiddling around with each particular expression. Seems like there should be a simpler way to automate the string -> input code conversion that works for arbitrary strings of (valid) code. – WillG Jul 21 '22 at 05:40
  • Made a new post: https://mathematica.stackexchange.com/questions/271091/how-to-convert-any-string-of-code-to-an-input-cell – WillG Jul 21 '22 at 05:51