Skip to content

Commit 114023d

Browse files
committed
Load magic for executing code from files or other notebooks.
1 parent ff29753 commit 114023d

File tree

1 file changed

+150
-0
lines changed
  • src/main/java/io/github/spencerpark/jupyter/kernel/magic/common

1 file changed

+150
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package io.github.spencerpark.jupyter.kernel.magic.common;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.GsonBuilder;
5+
import com.google.gson.stream.JsonReader;
6+
import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic;
7+
import io.github.spencerpark.jupyter.kernel.magic.registry.MagicsArgs;
8+
9+
import java.io.FileNotFoundException;
10+
import java.io.IOException;
11+
import java.io.Reader;
12+
import java.nio.charset.StandardCharsets;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
import java.nio.file.Paths;
16+
import java.util.Collections;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.function.Consumer;
20+
import java.util.stream.Collectors;
21+
22+
public class Load {
23+
@FunctionalInterface
24+
public static interface Executor {
25+
public void execute(String code) throws Exception;
26+
}
27+
28+
private static final ThreadLocal<Gson> GSON = ThreadLocal.withInitial(() ->
29+
new GsonBuilder().create());
30+
31+
private static final MagicsArgs LOAD_ARGS = MagicsArgs.builder()
32+
.required("source")
33+
.onlyKnownFlags().onlyKnownKeywords()
34+
.build();
35+
36+
// This slightly verbose implementation is designed to take advantage of gson as a streaming parser
37+
// in which we can only take what we need on the fly and pass each cell to the handler without needing
38+
// to keep the entire notebook in memory.
39+
// This should be a big help for larger notebooks.
40+
private static void forEachCell(Path notebookPath, Executor handle) throws Exception {
41+
try (Reader in = Files.newBufferedReader(notebookPath, StandardCharsets.UTF_8)) {
42+
JsonReader reader = GSON.get().newJsonReader(in);
43+
reader.beginObject();
44+
while (reader.hasNext()) {
45+
String name = reader.nextName();
46+
if (!name.equals("cells")) {
47+
reader.skipValue();
48+
continue;
49+
}
50+
51+
// Parsing cells
52+
reader.beginArray();
53+
while (reader.hasNext()) {
54+
Boolean isCode = null;
55+
String source = null;
56+
57+
reader.beginObject();
58+
while (reader.hasNext()) {
59+
// If the cell type was parsed and wasn't code, then don't
60+
// bother doing any more work. Skip the rest.
61+
if (isCode != null && !isCode) {
62+
reader.skipValue();
63+
continue;
64+
}
65+
66+
switch (reader.nextName()) {
67+
case "cell_type":
68+
// We are only concerned with code cells.
69+
String cellType = reader.nextString();
70+
isCode = cellType.equals("code");
71+
break;
72+
case "source":
73+
// "source" is an array of lines.
74+
StringBuilder srcBuilder = new StringBuilder();
75+
reader.beginArray();
76+
while (reader.hasNext())
77+
srcBuilder.append(reader.nextString());
78+
reader.endArray();
79+
source = srcBuilder.toString();
80+
break;
81+
default:
82+
reader.skipValue();
83+
break;
84+
}
85+
}
86+
reader.endObject();
87+
88+
// Found a code cell!
89+
if (isCode != null && isCode)
90+
handle.execute(source);
91+
}
92+
reader.endArray();
93+
}
94+
reader.endObject();
95+
}
96+
}
97+
98+
private final List<String> fileExtensions;
99+
private final Executor exec;
100+
101+
public Load(List<String> fileExtensions, Executor exec) {
102+
this.fileExtensions = fileExtensions == null
103+
? Collections.emptyList()
104+
: fileExtensions.stream()
105+
.map(e -> e.startsWith(".") ? e : "." + e)
106+
.collect(Collectors.toList());
107+
this.exec = exec;
108+
}
109+
110+
@LineMagic
111+
public void load(List<String> args) throws Exception {
112+
Map<String, List<String>> vals = LOAD_ARGS.parse(args);
113+
114+
Path sourcePath = Paths.get(vals.get("source").get(0)).toAbsolutePath();
115+
116+
if (Files.isRegularFile(sourcePath)) {
117+
if (sourcePath.getFileName().toString().endsWith(".ipynb")) {
118+
// Execute a notebook, run all cells in there.
119+
Load.forEachCell(sourcePath, this.exec);
120+
return;
121+
}
122+
123+
String sourceContents = new String(Files.readAllBytes(sourcePath), StandardCharsets.UTF_8);
124+
this.exec.execute(sourceContents);
125+
return;
126+
}
127+
128+
String file = sourcePath.getFileName().toString();
129+
130+
// Try and see if adding any of the supported extensions gives a file.
131+
for (String extension : this.fileExtensions) {
132+
Path scriptPath = sourcePath.resolveSibling(file + extension);
133+
if (Files.isRegularFile(scriptPath)) {
134+
String sourceContents = new String(Files.readAllBytes(scriptPath), StandardCharsets.UTF_8);
135+
this.exec.execute(sourceContents);
136+
return;
137+
}
138+
}
139+
140+
// Try a notebook last.
141+
Path scriptPath = sourcePath.resolveSibling(file + ".ipynb");
142+
if (Files.isRegularFile(scriptPath)) {
143+
// Execute a notebook, run all cells in there.
144+
Load.forEachCell(scriptPath, this.exec);
145+
return;
146+
}
147+
148+
throw new FileNotFoundException("Could not find any source at '" + sourcePath + "'. Also tried with extensions: [.ipynb, " + this.fileExtensions.stream().collect(Collectors.joining(", ")) + "].");
149+
}
150+
}

0 commit comments

Comments
 (0)