| | import static com.keenwrite.preferences.WorkspaceKeys.KEY_TYPESET_CONTEXT_ENV; |
| | import static com.keenwrite.preferences.WorkspaceKeys.KEY_TYPESET_CONTEXT_PATH; |
| | +import static java.lang.ProcessBuilder.Redirect.DISCARD; |
| | import static java.lang.String.format; |
| | import static java.lang.System.currentTimeMillis; |
 |
| | final var task = new TypesetTask( in, out ); |
| | final var time = currentTimeMillis(); |
| | - final var code = task.call(); |
| | - final var message = code == 0 |
| | - ? get( "Main.status.typeset.ended.success", out, since( time ) ) |
| | - : get( "Main.status.typeset.ended.failure", out, since( time ), code ); |
| | + final var success = task.typeset(); |
| | |
| | - clue( message ); |
| | + clue( get( |
| | + "Main.status.typeset.ended." + (success ? "success" : "failure"), |
| | + out, since( time ) ) |
| | + ); |
| | } |
| | } |
| | |
| | /** |
| | * Launches a task to typeset a document. |
| | */ |
| | - private class TypesetTask implements Callable<Integer> { |
| | + private class TypesetTask implements Callable<Boolean> { |
| | private final List<String> mArgs = new ArrayList<>(); |
| | + private final Path mInput; |
| | + private final Path mOutput; |
| | |
| | /** |
| | * Working directory must be set because ConTeXt cannot write the |
| | * result to an arbitrary location. |
| | */ |
| | private final Path mDirectory; |
| | |
| | - public TypesetTask( final Path input, final Path output ) { |
| | - final var filename = output.getFileName(); |
| | + private TypesetTask( final Path input, final Path output ) { |
| | + assert input != null; |
| | + assert output != null; |
| | + |
| | final var parentDir = output.getParent(); |
| | + mInput = input; |
| | + mOutput = output; |
| | mDirectory = (parentDir == null ? DEFAULT_DIRECTORY : parentDir); |
| | + } |
| | |
| | + /** |
| | + * Initializes ConTeXt, which means creating the cache directory if it |
| | + * doesn't already exist. |
| | + * |
| | + * @return {@code true} if the cache directory exists. |
| | + */ |
| | + private boolean reinitialize() { |
| | + final var filename = mOutput.getFileName(); |
| | final var paths = getProperty( KEY_TYPESET_CONTEXT_PATH ); |
| | final var envs = getProperty( KEY_TYPESET_CONTEXT_ENV ); |
| | + final var exists = getCacheDir().exists(); |
| | |
| | + // Ensure invoking multiple times will load the correct arguments. |
| | + mArgs.clear(); |
| | mArgs.add( TYPESETTER.getName() ); |
| | - mArgs.add( "--autogenerate" ); |
| | - mArgs.add( "--script" ); |
| | - mArgs.add( "mtx-context" ); |
| | - mArgs.add( "--batchmode" ); |
| | - mArgs.add( "--purgeall" ); |
| | - mArgs.add( "--path='" + paths + "'" ); |
| | - mArgs.add( "--environment='" + envs + "'" ); |
| | - mArgs.add( "--result='" + filename + "'" ); |
| | - mArgs.add( input.toString() ); |
| | + |
| | + if( exists ) { |
| | + mArgs.add( "--autogenerate" ); |
| | + mArgs.add( "--script" ); |
| | + mArgs.add( "mtx-context" ); |
| | + mArgs.add( "--batchmode" ); |
| | + mArgs.add( "--purgeall" ); |
| | + mArgs.add( "--path='" + paths + "'" ); |
| | + mArgs.add( "--environment='" + envs + "'" ); |
| | + mArgs.add( "--result='" + filename + "'" ); |
| | + mArgs.add( mInput.toString() ); |
| | + } |
| | + else { |
| | + mArgs.add( "--generate" ); |
| | + } |
| | + |
| | + return exists; |
| | + } |
| | + |
| | + /** |
| | + * Setting {@code TEXMFCACHE} when run on a fresh system fails on first |
| | + * run. If the cache directory doesn't exist, attempt to create it, then |
| | + * call ConTeXt to generate the PDF. |
| | + * |
| | + * @return {@code true} if the document was typeset successfully. |
| | + * @throws Exception If the typesetter could not be invoked. |
| | + */ |
| | + private boolean typeset() throws Exception { |
| | + return reinitialize() ? call() : call() && reinitialize() && call(); |
| | } |
| | |
| | @Override |
| | - public Integer call() throws Exception { |
| | + public Boolean call() throws Exception { |
| | final var builder = new ProcessBuilder( mArgs ); |
| | builder.directory( mDirectory.toFile() ); |
| | |
| | + final var cacheDir = getCacheDir(); |
| | final var env = builder.environment(); |
| | - env.put( "TEXMFCACHE", System.getProperty( "java.io.tmpdir" ) ); |
| | + env.put( "TEXMFCACHE", cacheDir.toString() ); |
| | |
| | - //final var process = builder.inheritIO().start(); |
| | + // Without redirecting (or draining) the output, the command will not |
| | + // terminate successfully. |
| | + builder.redirectOutput( DISCARD ); |
| | + builder.redirectError( DISCARD ); |
| | + |
| | final var process = builder.start(); |
| | process.waitFor(); |
| | - final int exit = process.exitValue(); |
| | + final var exit = process.exitValue(); |
| | process.destroy(); |
| | - return exit; |
| | + |
| | + // Exit value for a successful invocation of the typesetter. This value |
| | + // value is returned when creating the cache on the first run as well as |
| | + // creating PDFs on subsequent runs (after the cache has been created). |
| | + // Users don't care about exit codes, only whether the PDF was generated. |
| | + return exit == 0; |
| | + } |
| | + |
| | + /** |
| | + * Returns the location of the cache directory. |
| | + * |
| | + * @return A fully qualified path to the location to store temporary |
| | + * files between typesetting runs. |
| | + */ |
| | + private java.io.File getCacheDir() { |
| | + final var temp = System.getProperty( "java.io.tmpdir" ); |
| | + final var cache = Path.of( temp, "luatex-cache" ); |
| | + return cache.toFile(); |
| | } |
| | } |