Skip to content

Commit b1eb39d

Browse files
authored
Enhanced reporting of TOML parse errors (#28)
Convert TOML syntax errors detected by the parser to Exception. Related small adjustments. Add a test to TestJythonCli.
1 parent 47a2611 commit b1eb39d

File tree

3 files changed

+75
-32
lines changed

3 files changed

+75
-32
lines changed

JythonCli.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.tomlj.Toml;
99
import org.tomlj.TomlParseResult;
10+
import org.tomlj.TomlParseError;
1011

1112
public class JythonCli {
1213

@@ -108,6 +109,8 @@ void readJBangBlock(Reader script) throws IOException {
108109
LineNumberReader lines = new LineNumberReader(script);
109110
String line;
110111
boolean found = false;
112+
printIfDebug("");
113+
printIfDebug("TOML data in Jython script:");
111114
while ((line = lines.readLine())!=null) {
112115
int lineno = lines.getLineNumber();
113116
if (found && !line.startsWith("# ")) {
@@ -120,7 +123,10 @@ void readJBangBlock(Reader script) throws IOException {
120123
} else if (found && line.startsWith("# ///")) {
121124
printIfDebug(lineno, line);
122125
break;
123-
} else if (found && line.startsWith("# ")) {
126+
} else if (found && (line.startsWith("# ") || line.equals("#"))) {
127+
if (line.length() == 1) {
128+
line += " ";
129+
}
124130
printIfDebug(lineno, line);
125131
if (tomlText.length() > 0) {
126132
tomlText.append("\n");
@@ -141,8 +147,24 @@ void readJBangBlock(Reader script) throws IOException {
141147
void interpretJBangBlock() throws IOException {
142148

143149
if (tomlText.length() > 0) {
150+
int lineno = 0;
151+
printIfDebug("");
152+
printIfDebug("TOML data extracted from Jython script:");
153+
for (String line: tomlText.toString().split("\\n", -1)) {
154+
lineno += 1;
155+
printIfDebug(lineno, line);
156+
}
144157
tpr = Toml.parse(tomlText.toString());
145-
printIfDebug(tpr.toJson());
158+
if (tpr.hasErrors()) {
159+
for (TomlParseError err: tpr.errors()) {
160+
System.err.println(err.toString());
161+
}
162+
if (debug) {
163+
throw new IOException("Error interpreting JBang TOML data.");
164+
} else {
165+
throw new IOException("Error interpreting JBang TOML data. Re-run with '--cli-debug' for details.");
166+
}
167+
}
146168
}
147169

148170
// Process the TOML data

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,14 @@ On Linux or MacOS the JythonCli.java script can be run directly for testing purp
257257
* ./JythonCli.java -V
258258
* ./JythonCli.java examples/banner.py
259259

260+
## Java Source File Formatting
261+
262+
Use the `google-java-format` to format the `JythonCli.java` and `TestJythonCli.java` programs.
263+
264+
```
265+
jbang run com.google.googlejavaformat:google-java-format:1.29.0 --aosp -r *.java
266+
```
267+
260268
## Articles about Jython and JBang
261269

262270
The following articles describe the work that led to the creation of the `jython-cli` JBang script.

TestJythonCli.java

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class TestJythonCli {
2727
{"--version", "foo.py", "bar.py", "baz"};
2828
static final String[] ARGS_NONE = {"--cli-debug"};
2929

30-
/** The {@code --debug-cli} flag is spotted */
30+
/** The {@code --cli-debug} flag is spotted */
3131
@Test
3232
void testCliDebugFlag() throws IOException {
3333
JythonCli cli = new JythonCli();
@@ -59,11 +59,12 @@ void testJythonArgs() throws IOException {
5959
@Test
6060
@Disabled("readJBangBlock does not throw on an unterminated block")
6161
void testUnterminated() throws IOException {
62-
String script = """
63-
# /// jbang
64-
# requires-jython = "2.7.2"
65-
# requires-java = "17"
66-
import sys
62+
String script =
63+
"""
64+
# /// jbang
65+
# requires-jython = "2.7.2"
66+
# requires-java = "17"
67+
import sys
6768
""";
6869
JythonCli cli = new JythonCli();
6970
assertThrows(Exception.class, () -> processScript(cli, script));
@@ -75,15 +76,17 @@ void testUnterminated() throws IOException {
7576
* {@code jbang} header could be legitimate content.
7677
*/
7778
@Test
79+
@Disabled("readJBangBlock treats '/// jbang' inside another block as valid start")
7880
void testGobbledBlock() throws IOException {
7981
JythonCli cli = new JythonCli();
80-
processScript(cli, """
81-
# /// script
82-
# requires-python = ">=3.11"
83-
# /// jbang
84-
# requires-jython = "2.7.2"
85-
# requires-java = "8"
86-
# ///
82+
processScript(cli,
83+
"""
84+
# /// script
85+
# requires-python = ">=3.11"
86+
# /// jbang
87+
# requires-jython = "2.7.2"
88+
# requires-java = "8"
89+
# ///
8790
""");
8891
assertTrue(cli.tomlText.isEmpty(), "Check TOML text is empty");
8992
assertNull(cli.tpr, "Check TOML parse not done");
@@ -96,16 +99,17 @@ void testGobbledBlock() throws IOException {
9699
* block-start is not valid TOML.
97100
*/
98101
@Test
99-
@Disabled("interpretJBangBlock does not throw for invalid TOML")
102+
@Disabled("interpretJBangBlock treats '/// script' as valid terminator")
100103
void testCollision() throws IOException {
101-
String script = """
104+
String script =
105+
"""
102106
# /// jbang
103107
# requires-jython = "2.7.2"
104108
# requires-java = "8"
105109
# /// script
106110
# requires-python = ">=3.11"
107111
# ///
108-
""";
112+
""";
109113
JythonCli cli = new JythonCli();
110114
assertThrows(Exception.class, () -> processScript(cli, script));
111115
assertFalse(cli.tomlText.isEmpty(), "Detect TOML text is empty");
@@ -116,7 +120,8 @@ void testCollision() throws IOException {
116120
@Test
117121
@Disabled("readJBangBlock does not throw on a second jbang block")
118122
void testTwoBlocks() throws IOException {
119-
String script = """
123+
String script =
124+
"""
120125
# /// jbang
121126
# requires-jython = "2.7.2"
122127
# requires-java = "8"
@@ -130,27 +135,31 @@ void testTwoBlocks() throws IOException {
130135
# /// jbang
131136
# requires-jython = "2.7.3"
132137
# ///
133-
""";
138+
""";
134139
JythonCli cli = new JythonCli();
135140
assertThrows(Exception.class, () -> processScript(cli, script));
136141
}
137142

138143
/** Invalid TOML is an error. */
139144
@Test
140-
@Disabled("interpretJBangBlock does not throw for invalid TOML")
141145
void testInvalidTOML() throws IOException {
142-
String script = """
146+
String script =
147+
"""
143148
# /// jbang
144-
# requires-java = "8"
145-
# stuff = {
146-
# nonsense = 42
147-
# Quatsch =::
148-
# }
149+
# requires-jython = "2.7.4"
150+
# requires-java = "21"
151+
# dependencies = [
152+
# "io.leego:banana:2.1.0"
153+
# ]
154+
# runtime-options = [
155+
# "-Dpython.console.encoding=UTF-8"
156+
# -
149157
# ///
150158
print("Hello World!")
151-
""";
159+
""";
152160
JythonCli cli = new JythonCli();
153161
assertThrows(Exception.class, () -> processScript(cli, script));
162+
assertTrue(cli.tpr.hasErrors(), "Check TOML parse reports errors");
154163
}
155164

156165
/**
@@ -161,8 +170,7 @@ void testInvalidTOML() throws IOException {
161170
* @param script to process as script
162171
* @throws IOException on StringReader errors
163172
*/
164-
void processScript(JythonCli cli, String script)
165-
throws IOException {
173+
void processScript(JythonCli cli, String script) throws IOException {
166174
cli.initEnvironment(ARGS_NONE);
167175
cli.readJBangBlock(new StringReader(script));
168176
cli.interpretJBangBlock();
@@ -173,8 +181,13 @@ void processScript(JythonCli cli, String script)
173181
*
174182
* @param args to pass to the JUnit console
175183
*/
176-
public static void main(String[] args) {
184+
public static void main(String[] args) throws IOException {
177185
// Run the JUnit console
178-
ConsoleLauncher.main(args);
186+
if (true) {
187+
ConsoleLauncher.main(args);
188+
} else {
189+
// Debugging code
190+
new TestJythonCli().testInvalidTOML();
191+
}
179192
}
180193
}

0 commit comments

Comments
 (0)