Execute Java code, and capture output, to be included in a book written in AsciiDoc

109 Views Asked by At

I am writing a technical book for Java programming in AsciiDoc format, using the AsciiDoctor plugin for the IntelliJ IDE.

I have seen how to include snippets of code from a Java source code file.

I have heard of people also executing Java source code, then capturing output from that code for inclusion within the AsciiDoc content. But I have no idea how to do this.

Is running Java code and capturing output into AsciiDoc really possible? If so:

  • Should the Java code be included in a single project also containing the AsciiDoc content files? Or should the Java code and the AsciiDoc-formatted book content be contained in two separate distinct projects?
  • If the Java project is driven by Maven or Gradle, how do we hook AsciiDoc into the build process? When changing the Java source code, can we update the code snippet and freshen the output over in the AsciiDoc-formatted content files?
1

There are 1 best solutions below

0
Max On

Given how many points you have, I'm guessing you probably have some idea how to do this, and are hoping for something clever. But something like writing compiling books might be niche enough that you have to DIY something yourself. I'm kinda spitballing here about what I would personally do, so apologies in advance if the answer seems meh quality.

This problem can be broken into a few parts:

  1. How to represent the code?
  2. How to run the code, and capture the output?
  3. How to convert that output into something AsciiDoc can understand?
  4. How to actually wire it into the AsciiDoc build?

Representing the code

For each code snippet, I'd write a little class with a main method, and then demarcate the part you want to extract using comments, similar to how many static analysis tools use comments to toggle off/on.

public class MySnippet {
  // SNIPPET:ON
  public void mySnippet() {
    System.out.println("Hello world!");
  }
  // SNIPPET:OFF

  public static void main(String[] args) {
    mySnippet();
  }
}

This would be enough to handle snippets that directly stuff out. If you want your snippets to be expressions (instead of statements/methods)...that might be trickier. But even in the above case, you have options; you could return something and print it from the main method (so you don't have printlns in every snippet) or you could move the snippet comments inside the method.

Running the code

If you have a file src/main/org/example/MySnippet.java, you really just want to run org.example.MySnippet's main method. If you're using Gradle, the most galaxy-brain way to do this would probably be writing a small Gradle plugin that uses annotation processing, but there are scrappier ways to do this. I'd see about writing a Gradle task to scan for files in some particular directory (e.g. /src/main/org/example/*.java), convert those file paths into class names, and then dynamically generating JavaExec tasks during Gradle's configuration phase that will run every main method and write their output to different files. JavaExec has a standardOutput field that should prove useful. Fiddling with the whitespace indents might be a pain, in which case I suppose you could simply not indent those lines of code.

For each file found this way, you'd want to slice out those comments mentioned in the previous step, and perhaps write this out to the same stdout file before the JavaExec has a chance to run...depending on how exactly you want your snippets to look. In this case, it could be "Here's the code, then a horizontal rule, then the output". Emitting the output inline or near the line that executed it would be trickier.

Converting to AsciiDoc

Theoretically, at this point you have build/generated/snippets*.txt files or something with just the raw output. You could have another task that converts these txt files to adoc snippets, or...the previous step could just emit valid adoc snippets to begin with, which would save some trouble.

Wiring into AsciiDoc

You could, and long term maybe should, combine these into a single Gradle project, but it'd also be easy to make them separate projects that must be checked out next to each other -- book/ and book_samples/ as sibling directories. In your book, you'd just need to import ../../../ enough times to get into the build/generated/* of the other project.


I wasn't able to be too prescriptive here, but I hope this is still a good lead. You'll definitely have to learn some build tooling (e.g. custom Gradle/Maven tasks) if you haven't already, unless you do end up finding a tool that already does this.