Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
build.gradle
implementation 'org.greenrobot:eventbus-java:3.3.1'
+ // Logging.
+ implementation 'org.slf4j:slf4j-api:2.1.0-alpha1'
+ implementation 'org.slf4j:slf4j-nop:2.0.16'
+
// Command-line parsing
implementation "info.picocli:picocli:${v_picocli}"
tasks.withType( JavaCompile ).configureEach {
options.encoding = 'UTF-8'
-}
-
-tasks.withType( JavaExec ).configureEach {
- jvmArgs += '--enable-preview'
-}
-
-tasks.withType( Test ).configureEach {
- jvmArgs += '--enable-preview'
}
installer.sh
readonly SCRIPT_SRC="\$(dirname "\${BASH_SOURCE[\${#BASH_SOURCE[@]} - 1]}")"
-"\${SCRIPT_SRC}/${ARG_JAVA_DIR}/bin/java" ${OPT_JAVA} -jar "\${SCRIPT_SRC}/${FILE_APP_JAR}" "\$@" 2>/dev/null
+"\${SCRIPT_SRC}/${ARG_JAVA_DIR}/bin/java" ${OPT_JAVA} -jar "\${SCRIPT_SRC}/${FILE_APP_JAR}" "\$@"
__EOT
set SCRIPT_DIR=%~dp0
-"%SCRIPT_DIR%\\${ARG_JAVA_DIR}\\bin\\java" ${OPT_JAVA} -jar "%SCRIPT_DIR%\\${FILE_APP_JAR}" %* 2>nul
+"%SCRIPT_DIR%\\${ARG_JAVA_DIR}\\bin\\java" ${OPT_JAVA} -jar "%SCRIPT_DIR%\\${FILE_APP_JAR}" %*
__EOT
src/main/java/com/keenwrite/AppCommands.java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicInteger;
-import static com.keenwrite.Launcher.terminate;
import static com.keenwrite.events.StatusEvent.clue;
import static com.keenwrite.io.MediaType.TEXT_R_MARKDOWN;
public static void run( final Arguments args ) {
- final var exitCode = new AtomicInteger();
-
final var future = new CompletableFuture<Path>() {
@Override
public boolean complete( final Path path ) {
return super.complete( path );
}
@Override
public boolean completeExceptionally( final Throwable ex ) {
clue( ex );
- exitCode.set( 1 );
return super.completeExceptionally( ex );
}
};
file_export( args, future );
sExecutor.shutdown();
future.join();
- terminate( exitCode.get() );
}
src/main/java/com/keenwrite/GuiApp.java
+/* Copyright 2020-2024 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite;
+
+import com.keenwrite.cmdline.HeadlessApp;
+import com.keenwrite.events.HyperlinkOpenEvent;
+import com.keenwrite.preferences.Workspace;
+import com.keenwrite.preview.MathRenderer;
+import com.keenwrite.spelling.impl.Lexicon;
+import javafx.application.Application;
+import javafx.event.Event;
+import javafx.event.EventType;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.stage.Stage;
+import org.greenrobot.eventbus.Subscribe;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.function.BooleanSupplier;
+
+import static com.keenwrite.Bootstrap.APP_TITLE;
+import static com.keenwrite.constants.GraphicsConstants.LOGOS;
+import static com.keenwrite.events.Bus.register;
+import static com.keenwrite.preferences.AppKeys.*;
+import static com.keenwrite.util.FontLoader.initFonts;
+import static javafx.scene.input.KeyCode.F11;
+import static javafx.scene.input.KeyEvent.KEY_PRESSED;
+import static javafx.scene.input.KeyEvent.KEY_RELEASED;
+import static javafx.stage.WindowEvent.WINDOW_SHOWN;
+
+/**
+ * The application allows users to edit plain text files in a markup notation
+ * and see a real-time preview of the formatted output.
+ */
+public final class GuiApp extends Application {
+
+ private Workspace mWorkspace;
+
+ /**
+ * GUI application entry point. See {@link HeadlessApp} for the entry
+ * point to the command-line application.
+ *
+ * @param args Command-line arguments.
+ */
+ public static void run( final String[] args ) {
+ launch( args );
+ }
+
+ /**
+ * Creates an instance of {@link KeyEvent} that represents pressing a key.
+ *
+ * @param code The key to simulate being pressed down.
+ * @param shift Whether shift key modifier shall modify the key code.
+ * @return An instance of {@link KeyEvent} that may be used to simulate
+ * a key being pressed.
+ */
+ public static Event keyDown( final KeyCode code, final boolean shift ) {
+ return keyEvent( KEY_PRESSED, code, shift );
+ }
+
+ /**
+ * Creates an instance of {@link KeyEvent} that represents a key released
+ * event without any modifier keys held.
+ *
+ * @param code The key code representing a key to simulate releasing.
+ * @return An instance of {@link KeyEvent}.
+ */
+ public static Event keyDown( final KeyCode code ) {
+ return keyDown( code, false );
+ }
+
+ /**
+ * Creates an instance of {@link KeyEvent} that represents releasing a key.
+ *
+ * @param code The key to simulate being released up.
+ * @param shift Whether shift key modifier shall modify the key code.
+ * @return An instance of {@link KeyEvent} that may be used to simulate
+ * a key being released.
+ */
+ @SuppressWarnings( "unused" )
+ public static Event keyUp( final KeyCode code, final boolean shift ) {
+ return keyEvent( KEY_RELEASED, code, shift );
+ }
+
+ private static Event keyEvent(
+ final EventType<KeyEvent> type, final KeyCode code, final boolean shift ) {
+ return new KeyEvent(
+ type, "", "", code, shift, false, false, false
+ );
+ }
+
+ /**
+ * JavaFX entry point.
+ *
+ * @param stage The primary application stage.
+ */
+ @Override
+ public void start( final Stage stage ) {
+ // Must be instantiated after the UI is initialized (i.e., not in main)
+ // because it interacts with GUI properties.
+ mWorkspace = new Workspace();
+
+ // The locale was already loaded when the workspace was created. This
+ // ensures that when the locale preference changes, a new spellchecker
+ // instance will be loaded and applied.
+ final var property = mWorkspace.localeProperty( KEY_LANGUAGE_LOCALE );
+ property.addListener( ( _, _, _ ) -> readLexicon() );
+
+ initFonts();
+ initState( stage );
+ initStage( stage );
+ initIcons( stage );
+ initScene( stage );
+
+ MathRenderer.bindSize( mWorkspace.doubleProperty( KEY_UI_FONT_MATH_SIZE ) );
+
+ // Load the lexicon and check all the documents after all files are open.
+ stage.addEventFilter( WINDOW_SHOWN, _ -> readLexicon() );
+ stage.show();
+
+ register( this );
+ }
+
+ private void initState( final Stage stage ) {
+ final var enable = createBoundsEnabledSupplier( stage );
+
+ stage.setX( mWorkspace.getDouble( KEY_UI_WINDOW_X ) );
+ stage.setY( mWorkspace.getDouble( KEY_UI_WINDOW_Y ) );
+ stage.setWidth( mWorkspace.getDouble( KEY_UI_WINDOW_W ) );
+ stage.setHeight( mWorkspace.getDouble( KEY_UI_WINDOW_H ) );
+ stage.setMaximized( mWorkspace.getBoolean( KEY_UI_WINDOW_MAX ) );
+ stage.setFullScreen( mWorkspace.getBoolean( KEY_UI_WINDOW_FULL ) );
+
+ mWorkspace.listen( KEY_UI_WINDOW_X, stage.xProperty(), enable );
+ mWorkspace.listen( KEY_UI_WINDOW_Y, stage.yProperty(), enable );
+ mWorkspace.listen( KEY_UI_WINDOW_W, stage.widthProperty(), enable );
+ mWorkspace.listen( KEY_UI_WINDOW_H, stage.heightProperty(), enable );
+ mWorkspace.listen( KEY_UI_WINDOW_MAX, stage.maximizedProperty() );
+ mWorkspace.listen( KEY_UI_WINDOW_FULL, stage.fullScreenProperty() );
+ }
+
+ private void initStage( final Stage stage ) {
+ stage.setTitle( APP_TITLE );
+ stage.addEventHandler( KEY_PRESSED, event -> {
+ if( F11.equals( event.getCode() ) ) {
+ stage.setFullScreen( !stage.isFullScreen() );
+ }
+ } );
+ }
+
+ private void initIcons( final Stage stage ) {
+ stage.getIcons().addAll( LOGOS );
+ }
+
+ private void initScene( final Stage stage ) {
+ final var mainScene = new MainScene( mWorkspace );
+ stage.setScene( mainScene.getScene() );
+ }
+
+ /**
+ * When a hyperlink website URL is clicked, this method is called to launch
+ * the default browser to the event's location.
+ *
+ * @param event The event called when a hyperlink was clicked.
+ */
+ @Subscribe
+ public void handle( final HyperlinkOpenEvent event ) {
+ getHostServices().showDocument( event.getUri().toString() );
+ }
+
+ /**
+ * This will load the lexicon for the user's preferred locale and fire
+ * an event when the all entries in the lexicon have been loaded.
+ */
+ private void readLexicon() {
+ Lexicon.read( mWorkspace.getLocale() );
+ }
+
+ /**
+ * When the window is maximized, full screen, or iconified, prevent updating
+ * the window bounds. This is used so that if the user exits the application
+ * when full screen (or maximized), restarting the application will recall
+ * the previous bounds, allowing for continuity of expected behaviour.
+ *
+ * @param stage The window to check for "normal" status.
+ * @return {@code false} when the bounds must not be changed, ergo persisted.
+ */
+ private BooleanSupplier createBoundsEnabledSupplier( final Stage stage ) {
+ return () ->
+ !(stage.isMaximized() || stage.isFullScreen() || stage.isIconified());
+ }
+}
src/main/java/com/keenwrite/Launcher.java
-/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
+/* Copyright 2020-2024 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
package com.keenwrite;
import com.keenwrite.cmdline.Arguments;
import com.keenwrite.cmdline.ColourScheme;
import com.keenwrite.cmdline.HeadlessApp;
import picocli.CommandLine;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintStream;
import java.util.Properties;
import java.util.function.Consumer;
/**
- * Launches the application using the {@link MainApp} class.
+ * This is the main entry point to the application. The {@link Launcher} class
+ * is responsible for running the application using either the {@link GuiApp} or
+ * {@link HeadlessApp} class, depending on whether running with command-line
+ * arguments.
*
* <p>
* This is required until modules are implemented, which may never happen
* because the application should be ported away from Java and JavaFX.
* </p>
*/
public final class Launcher implements Consumer<Arguments> {
+ static {
+ // We don't care about the logging provider connection message.
+ System.setProperty( "slf4j.internal.verbosity", "WARN" );
+ }
/**
* Needed for the GUI.
*/
private final String[] mArgs;
+
+ /**
+ * Where to write error messages.
+ */
+ private static final PrintStream ERRORS = System.err;
/**
* @param exitCode Code to provide back to the calling shell.
*/
- public static void terminate( final int exitCode ) {
+ private static void terminate( final int exitCode ) {
System.exit( exitCode );
}
terminate( exitCode );
}
- else if( parseResult.isVersionHelpRequested() ) {
+
+ if( parseResult.isVersionHelpRequested() ) {
showAppInfo();
terminate( exitCode );
+ }
+
+ if( arguments.debug() ) {
+ arguments.iterate( parseResult, Launcher::log );
}
+
+ terminate( exitCode );
}
if( message != null && message.toLowerCase().contains( "javafx" ) ) {
message = "Run using a Java Runtime Environment that includes JavaFX.";
- out( "ERROR: %s", message );
+ log( "ERROR: %s", message );
}
else {
- error.printStackTrace( System.err );
+ error.printStackTrace( ERRORS );
}
}
/**
- * Suppress writing to standard error, suppresses writing log messages.
+ * Suppress writing log messages.
*/
private static void disableLogging() {
LogManager.getLogManager().reset();
- // TODO: Delete this after JavaFX/GTK 3 no longer barfs useless warnings.
- System.err.close();
}
/**
* Writes the given placeholder text to standard output with a new line
* appended.
*
* @param message The format string specifier.
* @param args The arguments to substitute into the format string.
*/
- private static void out( final String message, final Object... args ) {
- System.out.printf( format( "%s%n", message ), args );
+ private static void log( final String message, final Object... args ) {
+ ERRORS.printf( format( "%s%n", message ), args );
+ ERRORS.flush();
}
private static void showAppInfo() {
- out( "%n%s version %s", APP_TITLE, APP_VERSION );
- out( "Copyright 2016-%s White Magic Software, Ltd.", APP_YEAR );
- }
-
- /**
- * Delegates running the application via the command-line argument parser.
- * This is the main entry point for the application, regardless of whether
- * run from the command-line or as a GUI.
- *
- * @param args Command-line arguments.
- */
- public static void main( final String[] args ) {
- installTrustManager();
- parse( args );
+ log( "%s version %s", APP_TITLE, APP_VERSION );
+ log( "Copyright 2016-%s White Magic Software, Ltd.", APP_YEAR );
}
if( argCount <= 0 ) {
// When no command-line arguments are provided, launch the GUI.
- MainApp.main( mArgs );
+ GuiApp.run( mArgs );
}
else {
// When command-line arguments are supplied, run in headless mode.
- HeadlessApp.main( args );
+ HeadlessApp.run( args, ERRORS );
}
} catch( final Throwable t ) {
log( t );
}
+ }
+
+ /**
+ * Delegates running the application via the command-line argument parser.
+ * This is the main entry point for the application, regardless of whether
+ * run from the command-line or as a GUI.
+ *
+ * @param args Command-line arguments.
+ */
+ public static void main( final String[] args ) {
+ installTrustManager();
+ parse( args );
}
}
src/main/java/com/keenwrite/MainApp.java
-/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
-package com.keenwrite;
-
-import com.keenwrite.cmdline.HeadlessApp;
-import com.keenwrite.events.HyperlinkOpenEvent;
-import com.keenwrite.preferences.Workspace;
-import com.keenwrite.preview.MathRenderer;
-import com.keenwrite.spelling.impl.Lexicon;
-import javafx.application.Application;
-import javafx.event.Event;
-import javafx.event.EventType;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyEvent;
-import javafx.stage.Stage;
-import org.greenrobot.eventbus.Subscribe;
-
-import java.io.PrintStream;
-import java.util.function.BooleanSupplier;
-
-import static com.keenwrite.Bootstrap.APP_TITLE;
-import static com.keenwrite.constants.GraphicsConstants.LOGOS;
-import static com.keenwrite.events.Bus.register;
-import static com.keenwrite.preferences.AppKeys.*;
-import static com.keenwrite.util.FontLoader.initFonts;
-import static javafx.scene.input.KeyCode.F11;
-import static javafx.scene.input.KeyEvent.KEY_PRESSED;
-import static javafx.scene.input.KeyEvent.KEY_RELEASED;
-import static javafx.stage.WindowEvent.WINDOW_SHOWN;
-
-/**
- * Application entry point. The application allows users to edit plain text
- * files in a markup notation and see a real-time preview of the formatted
- * output.
- */
-public final class MainApp extends Application {
-
- private Workspace mWorkspace;
-
- /**
- * TODO: Delete this after JavaFX/GTK 3 no longer barfs useless warnings.
- */
- @SuppressWarnings( "SameParameterValue" )
- private static void stderrRedirect( final PrintStream stream ) {
- System.setErr( stream );
- }
-
- /**
- * GUI application entry point. See {@link HeadlessApp} for the entry
- * point to the command-line application.
- *
- * @param args Command-line arguments.
- */
- public static void main( final String[] args ) {
- launch( args );
- }
-
- /**
- * Creates an instance of {@link KeyEvent} that represents pressing a key.
- *
- * @param code The key to simulate being pressed down.
- * @param shift Whether shift key modifier shall modify the key code.
- * @return An instance of {@link KeyEvent} that may be used to simulate
- * a key being pressed.
- */
- public static Event keyDown( final KeyCode code, final boolean shift ) {
- return keyEvent( KEY_PRESSED, code, shift );
- }
-
- /**
- * Creates an instance of {@link KeyEvent} that represents a key released
- * event without any modifier keys held.
- *
- * @param code The key code representing a key to simulate releasing.
- * @return An instance of {@link KeyEvent}.
- */
- public static Event keyDown( final KeyCode code ) {
- return keyDown( code, false );
- }
-
- /**
- * Creates an instance of {@link KeyEvent} that represents releasing a key.
- *
- * @param code The key to simulate being released up.
- * @param shift Whether shift key modifier shall modify the key code.
- * @return An instance of {@link KeyEvent} that may be used to simulate
- * a key being released.
- */
- @SuppressWarnings( "unused" )
- public static Event keyUp( final KeyCode code, final boolean shift ) {
- return keyEvent( KEY_RELEASED, code, shift );
- }
-
- private static Event keyEvent(
- final EventType<KeyEvent> type, final KeyCode code, final boolean shift ) {
- return new KeyEvent(
- type, "", "", code, shift, false, false, false
- );
- }
-
- /**
- * JavaFX entry point.
- *
- * @param stage The primary application stage.
- */
- @Override
- public void start( final Stage stage ) {
- // Must be instantiated after the UI is initialized (i.e., not in main)
- // because it interacts with GUI properties.
- mWorkspace = new Workspace();
-
- // The locale was already loaded when the workspace was created. This
- // ensures that when the locale preference changes, a new spellchecker
- // instance will be loaded and applied.
- final var property = mWorkspace.localeProperty( KEY_LANGUAGE_LOCALE );
- property.addListener( ( _, _, _ ) -> readLexicon() );
-
- initFonts();
- initState( stage );
- initStage( stage );
- initIcons( stage );
- initScene( stage );
-
- MathRenderer.bindSize( mWorkspace.doubleProperty( KEY_UI_FONT_MATH_SIZE ) );
-
- // Load the lexicon and check all the documents after all files are open.
- stage.addEventFilter( WINDOW_SHOWN, _ -> readLexicon() );
- stage.show();
-
- stderrRedirect( System.out );
-
- register( this );
- }
-
- private void initState( final Stage stage ) {
- final var enable = createBoundsEnabledSupplier( stage );
-
- stage.setX( mWorkspace.getDouble( KEY_UI_WINDOW_X ) );
- stage.setY( mWorkspace.getDouble( KEY_UI_WINDOW_Y ) );
- stage.setWidth( mWorkspace.getDouble( KEY_UI_WINDOW_W ) );
- stage.setHeight( mWorkspace.getDouble( KEY_UI_WINDOW_H ) );
- stage.setMaximized( mWorkspace.getBoolean( KEY_UI_WINDOW_MAX ) );
- stage.setFullScreen( mWorkspace.getBoolean( KEY_UI_WINDOW_FULL ) );
-
- mWorkspace.listen( KEY_UI_WINDOW_X, stage.xProperty(), enable );
- mWorkspace.listen( KEY_UI_WINDOW_Y, stage.yProperty(), enable );
- mWorkspace.listen( KEY_UI_WINDOW_W, stage.widthProperty(), enable );
- mWorkspace.listen( KEY_UI_WINDOW_H, stage.heightProperty(), enable );
- mWorkspace.listen( KEY_UI_WINDOW_MAX, stage.maximizedProperty() );
- mWorkspace.listen( KEY_UI_WINDOW_FULL, stage.fullScreenProperty() );
- }
-
- private void initStage( final Stage stage ) {
- stage.setTitle( APP_TITLE );
- stage.addEventHandler( KEY_PRESSED, event -> {
- if( F11.equals( event.getCode() ) ) {
- stage.setFullScreen( !stage.isFullScreen() );
- }
- } );
- }
-
- private void initIcons( final Stage stage ) {
- stage.getIcons().addAll( LOGOS );
- }
-
- private void initScene( final Stage stage ) {
- final var mainScene = new MainScene( mWorkspace );
- stage.setScene( mainScene.getScene() );
- }
-
- /**
- * When a hyperlink website URL is clicked, this method is called to launch
- * the default browser to the event's location.
- *
- * @param event The event called when a hyperlink was clicked.
- */
- @Subscribe
- public void handle( final HyperlinkOpenEvent event ) {
- getHostServices().showDocument( event.getUri().toString() );
- }
-
- /**
- * This will load the lexicon for the user's preferred locale and fire
- * an event when the all entries in the lexicon have been loaded.
- */
- private void readLexicon() {
- Lexicon.read( mWorkspace.getLocale() );
- }
-
- /**
- * When the window is maximized, full screen, or iconified, prevent updating
- * the window bounds. This is used so that if the user exits the application
- * when full screen (or maximized), restarting the application will recall
- * the previous bounds, allowing for continuity of expected behaviour.
- *
- * @param stage The window to check for "normal" status.
- * @return {@code false} when the bounds must not be changed, ergo persisted.
- */
- private BooleanSupplier createBoundsEnabledSupplier( final Stage stage ) {
- return () ->
- !(stage.isMaximized() || stage.isFullScreen() || stage.isIconified());
- }
-}
src/main/java/com/keenwrite/MainPane.java
import com.keenwrite.preferences.Workspace;
import com.keenwrite.preview.HtmlPreview;
-import com.keenwrite.processors.html.HtmlPreviewProcessor;
import com.keenwrite.processors.Processor;
import com.keenwrite.processors.ProcessorContext;
import com.keenwrite.processors.ProcessorFactory;
+import com.keenwrite.processors.html.HtmlPreviewProcessor;
import com.keenwrite.processors.r.Engine;
import com.keenwrite.processors.r.RBootstrapController;
import static com.keenwrite.ExportFormat.NONE;
-import static com.keenwrite.Launcher.terminate;
import static com.keenwrite.Messages.get;
import static com.keenwrite.constants.Constants.*;
import static com.keenwrite.events.Bus.register;
import static com.keenwrite.events.StatusEvent.clue;
import static com.keenwrite.io.MediaType.*;
import static com.keenwrite.io.MediaType.TypeName.TEXT;
import static com.keenwrite.io.SysFile.toFile;
import static com.keenwrite.preferences.AppKeys.*;
-import static com.keenwrite.processors.html.IdentityProcessor.IDENTITY;
import static com.keenwrite.processors.ProcessorContext.Mutator;
import static com.keenwrite.processors.ProcessorContext.builder;
import static com.keenwrite.processors.ProcessorFactory.createProcessors;
+import static com.keenwrite.processors.html.IdentityProcessor.IDENTITY;
import static java.awt.Desktop.getDesktop;
import static java.util.concurrent.Executors.newFixedThreadPool;
if( closeAll() ) {
exit();
- terminate( 0 );
}
src/main/java/com/keenwrite/MainScene.java
/**
- * Called by the {@link MainApp} to get a handle on the {@link Scene}
+ * Called by the {@link GuiApp} to get a handle on the {@link Scene}
* created by an instance of {@link MainScene}.
*
src/main/java/com/keenwrite/cmdline/Arguments.java
-/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
- *
- * SPDX-License-Identifier: MIT
- */
-package com.keenwrite.cmdline;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
-import com.keenwrite.ExportFormat;
-import com.keenwrite.processors.ProcessorContext;
-import com.keenwrite.processors.ProcessorContext.Mutator;
-import picocli.CommandLine;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.Callable;
-import java.util.function.Consumer;
-
-import static com.keenwrite.constants.Constants.DIAGRAM_SERVER_NAME;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-/**
- * Responsible for mapping command-line arguments to keys that are used by
- * the application.
- */
-@CommandLine.Command(
- name = "KeenWrite",
- mixinStandardHelpOptions = true,
- description = "Plain text editor for editing with variables"
-)
-@SuppressWarnings( "unused" )
-public final class Arguments implements Callable<Integer> {
- @CommandLine.Option(
- names = { "--all" },
- description =
- "Concatenate files before processing (${DEFAULT-VALUE})",
- defaultValue = "false"
- )
- private boolean mConcatenate;
-
- @CommandLine.Option(
- names = { "--keep-files" },
- description =
- "Retain temporary build files (${DEFAULT-VALUE})",
- defaultValue = "false"
- )
- private boolean mKeepFiles;
-
- @CommandLine.Option(
- names = { "-c", "--chapters" },
- description =
- "Export chapter ranges, no spaces (e.g., -3,5-9,15-)",
- paramLabel = "String"
- )
- private String mChapters;
-
- @CommandLine.Option(
- names = { "--curl-quotes" },
- description =
- "Replace straight quotes with curly quotes (${DEFAULT-VALUE})",
- defaultValue = "true"
- )
- private boolean mCurlQuotes;
-
- @CommandLine.Option(
- names = { "-d", "--debug" },
- description =
- "Enable logging to the console (${DEFAULT-VALUE})",
- paramLabel = "Boolean",
- defaultValue = "false"
- )
- private boolean mDebug;
-
- @CommandLine.Option(
- names = { "-i", "--input" },
- description =
- "Source document file path",
- paramLabel = "PATH",
- defaultValue = "stdin",
- required = true
- )
- private Path mSourcePath;
-
- @CommandLine.Option(
- names = { "--font-dir" },
- description =
- "Directory to specify additional fonts",
- paramLabel = "String"
- )
- private File mFontDir;
-
- @CommandLine.Option(
- names = { "--mode" },
- description =
- "Enable one or more modes when typesetting",
- paramLabel = "String"
- )
- private String mTypesetMode;
-
- @CommandLine.Option(
- names = { "--format-subtype" },
- description =
- "Export TeX subtype for HTML formats: svg, delimited",
- paramLabel = "String",
- defaultValue = "svg"
- )
- private String mFormatSubtype;
-
- @CommandLine.Option(
- names = { "--cache-dir" },
- description =
- "Directory to store remote resources",
- paramLabel = "DIR"
- )
- private File mCachesDir;
-
- @CommandLine.Option(
- names = { "--image-dir" },
- description =
- "Directory containing images",
- paramLabel = "DIR"
- )
- private File mImagesDir;
-
- @CommandLine.Option(
- names = { "--image-order" },
- description =
- "Comma-separated image order (${DEFAULT-VALUE})",
- paramLabel = "String",
- defaultValue = "svg,pdf,png,jpg,tiff"
- )
- private String mImageOrder;
-
- @CommandLine.Option(
- names = { "--image-server" },
- description =
- "SVG diagram rendering service (${DEFAULT-VALUE})",
- paramLabel = "String",
- defaultValue = DIAGRAM_SERVER_NAME
- )
- private String mImageServer;
-
- @CommandLine.Option(
- names = { "--locale" },
- description =
- "Set localization (${DEFAULT-VALUE})",
- paramLabel = "String",
- defaultValue = "en"
- )
- private String mLocale;
-
- @CommandLine.Option(
- names = { "-m", "--metadata" },
- description =
- "Map metadata keys to values, variable names allowed",
- paramLabel = "key=value"
- )
- private Map<String, String> mMetadata;
-
- @CommandLine.Option(
- names = { "-o", "--output" },
- description =
- "Destination document file path",
- paramLabel = "PATH",
- defaultValue = "stdout",
- required = true
- )
- private Path mTargetPath;
-
- @CommandLine.Option(
- names = { "-q", "--quiet" },
- description =
- "Suppress all status messages (${DEFAULT-VALUE})",
- defaultValue = "false"
- )
- private boolean mQuiet;
-
- @CommandLine.Option(
- names = { "--r-dir" },
- description =
- "R working directory",
- paramLabel = "DIR"
- )
- private Path mRWorkingDir;
-
- @CommandLine.Option(
- names = { "--r-script" },
- description =
- "R bootstrap script file path",
- paramLabel = "PATH"
- )
- private Path mRScriptPath;
-
- @CommandLine.Option(
- names = { "-s", "--set" },
- description =
- "Set (or override) a document variable value",
- paramLabel = "key=value"
- )
- private Map<String, String> mOverrides;
-
- @CommandLine.Option(
- names = { "--sigil-opening" },
- description =
- "Starting sigil for variable names (${DEFAULT-VALUE})",
- paramLabel = "String",
- defaultValue = "{{"
- )
- private String mSigilBegan;
-
- @CommandLine.Option(
- names = { "--sigil-closing" },
- description =
- "Ending sigil for variable names (${DEFAULT-VALUE})",
- paramLabel = "String",
- defaultValue = "}}"
- )
- private String mSigilEnded;
-
- @CommandLine.Option(
- names = { "--theme-dir" },
- description =
- "Theme directory",
- paramLabel = "DIR"
- )
- private Path mThemesDir;
-
- @CommandLine.Option(
- names = { "-v", "--variables" },
- description =
- "Variables file path",
- paramLabel = "PATH"
- )
- private Path mPathVariables;
-
- private final Consumer<Arguments> mLauncher;
-
- public Arguments( final Consumer<Arguments> launcher ) {
- mLauncher = launcher;
- }
-
- public ProcessorContext createProcessorContext()
- throws IOException {
- final var definitions = parse( mPathVariables );
- final var format = ExportFormat.valueFrom( mTargetPath, mFormatSubtype );
- final var locale = lookupLocale( mLocale );
- final var rScript = read( mRScriptPath );
-
- return ProcessorContext
- .builder()
- .with( Mutator::setSourcePath, mSourcePath )
- .with( Mutator::setTargetPath, mTargetPath )
- .with( Mutator::setThemeDir, () -> mThemesDir )
- .with( Mutator::setCacheDir, () -> mCachesDir )
- .with( Mutator::setImageDir, () -> mImagesDir )
- .with( Mutator::setImageServer, () -> mImageServer )
- .with( Mutator::setImageOrder, () -> mImageOrder )
- .with( Mutator::setFontDir, () -> mFontDir )
- .with( Mutator::setModesEnabled, () -> mTypesetMode )
- .with( Mutator::setExportFormat, format )
- .with( Mutator::setDefinitions, () -> definitions )
- .with( Mutator::setMetadata, () -> mMetadata )
- .with( Mutator::setOverrides, () -> mOverrides )
- .with( Mutator::setLocale, () -> locale )
- .with( Mutator::setConcatenate, () -> mConcatenate )
- .with( Mutator::setChapters, () -> mChapters )
- .with( Mutator::setSigilBegan, () -> mSigilBegan )
- .with( Mutator::setSigilEnded, () -> mSigilEnded )
- .with( Mutator::setRScript, () -> rScript )
- .with( Mutator::setRWorkingDir, () -> mRWorkingDir )
- .with( Mutator::setCurlQuotes, () -> mCurlQuotes )
- .with( Mutator::setAutoRemove, () -> !mKeepFiles )
- .build();
- }
-
- public boolean quiet() {
- return mQuiet;
- }
-
- public boolean debug() {
- return mDebug;
- }
-
- /**
- * Launches the main application window. This is called when not running
- * in headless mode.
- *
- * @return {@code 0}
- * @throws Exception The application encountered an unrecoverable error.
- */
- @Override
- public Integer call() throws Exception {
- mLauncher.accept( this );
- return 0;
- }
-
- private static String read( final Path path ) throws IOException {
- return path == null ? "" : Files.readString( path, UTF_8 );
- }
-
- /**
- * Parses the given YAML document into a map of key-value pairs.
- *
- * @param vars Variable definition file to read, may be {@code null} if no
- * variables are specified.
- * @return A non-interpolated variable map, or an empty map.
- * @throws IOException Could not read the variable definition file
- */
- private static Map<String, String> parse( final Path vars )
- throws IOException {
- final var map = new HashMap<String, String>();
-
- if( vars != null ) {
- final var yaml = read( vars );
- final var factory = new YAMLFactory();
- final var json = new ObjectMapper( factory ).readTree( yaml );
-
- parse( json, "", map );
- }
-
- return map;
- }
-
- private static void parse(
- final JsonNode json, final String parent, final Map<String, String> map ) {
- assert json != null;
- assert parent != null;
- assert map != null;
-
- json.fields().forEachRemaining( node -> parse( node, parent, map ) );
- }
-
- private static void parse(
- final Entry<String, JsonNode> node,
- final String parent,
- final Map<String, String> map ) {
- assert node != null;
- assert parent != null;
- assert map != null;
-
- final var jsonNode = node.getValue();
- final var keyName = String.format( "%s.%s", parent, node.getKey() );
+/* Copyright 2023-2024 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+package com.keenwrite.cmdline;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.keenwrite.ExportFormat;
+import com.keenwrite.processors.ProcessorContext;
+import com.keenwrite.processors.ProcessorContext.Mutator;
+import picocli.CommandLine;
+import picocli.CommandLine.ParseResult;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.Callable;
+import java.util.function.Consumer;
+
+import static com.keenwrite.constants.Constants.DIAGRAM_SERVER_NAME;
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Responsible for mapping command-line arguments to keys that are used by
+ * the application.
+ */
+@CommandLine.Command(
+ name = "KeenWrite",
+ mixinStandardHelpOptions = true,
+ description = "Plain text editor for editing with variables"
+)
+@SuppressWarnings( "unused" )
+public final class Arguments implements Callable<Integer> {
+ @CommandLine.Spec
+ CommandLine.Model.CommandSpec mSpecifications;
+
+ @CommandLine.Option(
+ names = { "--all" },
+ description =
+ "Concatenate files before processing (${DEFAULT-VALUE})",
+ defaultValue = "false"
+ )
+ private boolean mConcatenate;
+
+ @CommandLine.Option(
+ names = { "--keep-files" },
+ description =
+ "Retain temporary build files (${DEFAULT-VALUE})",
+ defaultValue = "false"
+ )
+ private boolean mKeepFiles;
+
+ @CommandLine.Option(
+ names = { "-c", "--chapters" },
+ description =
+ "Export chapter ranges, no spaces (e.g., -3,5-9,15-)",
+ paramLabel = "String"
+ )
+ private String mChapters;
+
+ @CommandLine.Option(
+ names = { "--curl-quotes" },
+ description =
+ "Replace straight quotes with curly quotes (${DEFAULT-VALUE})",
+ defaultValue = "true"
+ )
+ private boolean mCurlQuotes;
+
+ @CommandLine.Option(
+ names = { "-d", "--debug" },
+ description =
+ "Enable logging to the console (${DEFAULT-VALUE})",
+ paramLabel = "Boolean",
+ defaultValue = "false"
+ )
+ private boolean mDebug;
+
+ @CommandLine.Option(
+ names = { "-i", "--input" },
+ description =
+ "Source document file path",
+ paramLabel = "PATH",
+ defaultValue = "stdin",
+ required = true
+ )
+ private Path mSourcePath;
+
+ @CommandLine.Option(
+ names = { "--font-dir" },
+ description =
+ "Directory to specify additional fonts",
+ paramLabel = "String"
+ )
+ private File mFontDir;
+
+ @CommandLine.Option(
+ names = { "--mode" },
+ description =
+ "Enable one or more modes when typesetting",
+ paramLabel = "String"
+ )
+ private String mTypesetMode;
+
+ @CommandLine.Option(
+ names = { "--format-subtype" },
+ description =
+ "Export TeX subtype for HTML formats: svg, delimited",
+ paramLabel = "String",
+ defaultValue = "svg"
+ )
+ private String mFormatSubtype;
+
+ @CommandLine.Option(
+ names = { "--cache-dir" },
+ description =
+ "Directory to store remote resources",
+ paramLabel = "DIR"
+ )
+ private File mCachesDir;
+
+ @CommandLine.Option(
+ names = { "--image-dir" },
+ description =
+ "Directory containing images",
+ paramLabel = "DIR"
+ )
+ private File mImagesDir;
+
+ @CommandLine.Option(
+ names = { "--image-order" },
+ description =
+ "Comma-separated image order (${DEFAULT-VALUE})",
+ paramLabel = "String",
+ defaultValue = "svg,pdf,png,jpg,tiff"
+ )
+ private String mImageOrder;
+
+ @CommandLine.Option(
+ names = { "--image-server" },
+ description =
+ "SVG diagram rendering service (${DEFAULT-VALUE})",
+ paramLabel = "String",
+ defaultValue = DIAGRAM_SERVER_NAME
+ )
+ private String mImageServer;
+
+ @CommandLine.Option(
+ names = { "--locale" },
+ description =
+ "Set localization (${DEFAULT-VALUE})",
+ paramLabel = "String",
+ defaultValue = "en"
+ )
+ private String mLocale;
+
+ @CommandLine.Option(
+ names = { "-m", "--metadata" },
+ description =
+ "Map metadata keys to values, variable names allowed",
+ paramLabel = "key=value"
+ )
+ private Map<String, String> mMetadata;
+
+ @CommandLine.Option(
+ names = { "-o", "--output" },
+ description =
+ "Destination document file path",
+ paramLabel = "PATH",
+ defaultValue = "stdout",
+ required = true
+ )
+ private Path mTargetPath;
+
+ @CommandLine.Option(
+ names = { "-q", "--quiet" },
+ description =
+ "Suppress all status messages (${DEFAULT-VALUE})",
+ defaultValue = "false"
+ )
+ private boolean mQuiet;
+
+ @CommandLine.Option(
+ names = { "--r-dir" },
+ description =
+ "R working directory",
+ paramLabel = "DIR"
+ )
+ private Path mRWorkingDir;
+
+ @CommandLine.Option(
+ names = { "--r-script" },
+ description =
+ "R bootstrap script file path",
+ paramLabel = "PATH"
+ )
+ private Path mRScriptPath;
+
+ @CommandLine.Option(
+ names = { "-s", "--set" },
+ description =
+ "Set (or override) a document variable value",
+ paramLabel = "key=value"
+ )
+ private Map<String, String> mOverrides;
+
+ @CommandLine.Option(
+ names = { "--sigil-opening" },
+ description =
+ "Starting sigil for variable names (${DEFAULT-VALUE})",
+ paramLabel = "String",
+ defaultValue = "{{"
+ )
+ private String mSigilBegan;
+
+ @CommandLine.Option(
+ names = { "--sigil-closing" },
+ description =
+ "Ending sigil for variable names (${DEFAULT-VALUE})",
+ paramLabel = "String",
+ defaultValue = "}}"
+ )
+ private String mSigilEnded;
+
+ @CommandLine.Option(
+ names = { "--theme-dir" },
+ description =
+ "Theme directory",
+ paramLabel = "DIR"
+ )
+ private Path mThemesDir;
+
+ @CommandLine.Option(
+ names = { "-v", "--variables" },
+ description =
+ "Variables file path",
+ paramLabel = "PATH"
+ )
+ private Path mPathVariables;
+
+ private final Consumer<Arguments> mLauncher;
+
+ public Arguments( final Consumer<Arguments> launcher ) {
+ mLauncher = launcher;
+ }
+
+ public ProcessorContext createProcessorContext()
+ throws IOException {
+ final var definitions = parse( mPathVariables );
+ final var format = ExportFormat.valueFrom( mTargetPath, mFormatSubtype );
+ final var locale = lookupLocale( mLocale );
+ final var rScript = read( mRScriptPath );
+
+ return ProcessorContext
+ .builder()
+ .with( Mutator::setSourcePath, mSourcePath )
+ .with( Mutator::setTargetPath, mTargetPath )
+ .with( Mutator::setThemeDir, () -> mThemesDir )
+ .with( Mutator::setCacheDir, () -> mCachesDir )
+ .with( Mutator::setImageDir, () -> mImagesDir )
+ .with( Mutator::setImageServer, () -> mImageServer )
+ .with( Mutator::setImageOrder, () -> mImageOrder )
+ .with( Mutator::setFontDir, () -> mFontDir )
+ .with( Mutator::setModesEnabled, () -> mTypesetMode )
+ .with( Mutator::setExportFormat, format )
+ .with( Mutator::setDefinitions, () -> definitions )
+ .with( Mutator::setMetadata, () -> mMetadata )
+ .with( Mutator::setOverrides, () -> mOverrides )
+ .with( Mutator::setLocale, () -> locale )
+ .with( Mutator::setConcatenate, () -> mConcatenate )
+ .with( Mutator::setChapters, () -> mChapters )
+ .with( Mutator::setSigilBegan, () -> mSigilBegan )
+ .with( Mutator::setSigilEnded, () -> mSigilEnded )
+ .with( Mutator::setRScript, () -> rScript )
+ .with( Mutator::setRWorkingDir, () -> mRWorkingDir )
+ .with( Mutator::setCurlQuotes, () -> mCurlQuotes )
+ .with( Mutator::setAutoRemove, () -> !mKeepFiles )
+ .build();
+ }
+
+ public boolean quiet() {
+ return mQuiet;
+ }
+
+ public boolean debug() {
+ return mDebug;
+ }
+
+ /**
+ * Launches the main application window. This is called when not running
+ * in headless mode.
+ *
+ * @return {@code 0}
+ * @throws Exception The application encountered an unrecoverable error.
+ */
+ @Override
+ public Integer call() throws Exception {
+ mLauncher.accept( this );
+ return 0;
+ }
+
+ public void iterate(
+ final ParseResult parseResult,
+ final Consumer<String> consumer
+ ) {
+ final var options = mSpecifications.options();
+
+ for( final var opt : options ) {
+ consumer.accept( format( "%s=%s", opt.longestName(), opt.getValue() ) );
+ }
+ }
+
+ private static String read( final Path path ) throws IOException {
+ return path == null ? "" : Files.readString( path, UTF_8 );
+ }
+
+ /**
+ * Parses the given YAML document into a map of key-value pairs.
+ *
+ * @param vars Variable definition file to read, may be {@code null} if no
+ * variables are specified.
+ * @return A non-interpolated variable map, or an empty map.
+ * @throws IOException Could not read the variable definition file
+ */
+ private static Map<String, String> parse( final Path vars )
+ throws IOException {
+ final var map = new HashMap<String, String>();
+
+ if( vars != null ) {
+ final var yaml = read( vars );
+ final var factory = new YAMLFactory();
+ final var json = new ObjectMapper( factory ).readTree( yaml );
+
+ parse( json, "", map );
+ }
+
+ return map;
+ }
+
+ private static void parse(
+ final JsonNode json, final String parent, final Map<String, String> map ) {
+ assert json != null;
+ assert parent != null;
+ assert map != null;
+
+ json.fields().forEachRemaining( node -> parse( node, parent, map ) );
+ }
+
+ private static void parse(
+ final Entry<String, JsonNode> node,
+ final String parent,
+ final Map<String, String> map ) {
+ assert node != null;
+ assert parent != null;
+ assert map != null;
+
+ final var jsonNode = node.getValue();
+ final var keyName = format( "%s.%s", parent, node.getKey() );
if( jsonNode.isValueNode() ) {
src/main/java/com/keenwrite/cmdline/HeadlessApp.java
+/* Copyright 2020-2024 White Magic Software, Ltd. -- All rights reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ */
package com.keenwrite.cmdline;
import com.keenwrite.AppCommands;
import com.keenwrite.events.StatusEvent;
import org.greenrobot.eventbus.Subscribe;
+
+import java.io.PrintStream;
import static com.keenwrite.events.Bus.register;
import static java.lang.String.format;
/**
* Responsible for running the application in headless mode.
*/
public class HeadlessApp {
-
/**
* Contains directives that control text file processing.
*/
private final Arguments mArgs;
+
+ /**
+ * Where to write error messages.
+ */
+ private final PrintStream mErrStream;
/**
* Creates a new command-line version of the application.
*
- * @param args The post-processed command-line arguments.
+ * @param args The post-processed command-line arguments.
+ * @param errStream Where to write error messages.
*/
- public HeadlessApp( final Arguments args ) {
+ public HeadlessApp( final Arguments args, final PrintStream errStream ) {
assert args != null;
mArgs = args;
+ mErrStream = errStream;
register( this );
- AppCommands.run( mArgs );
}
final var msg = format( "%s%s", event, problem );
- System.out.println( msg );
+ mErrStream.println( msg );
}
+ }
+
+ private void run() {
+ AppCommands.run( mArgs );
}
/**
* Entry point for running the application in headless mode.
*
- * @param args The parsed command-line arguments.
+ * @param args The parsed command-line arguments.
+ * @param errStream Where to write error messages.
*/
- @SuppressWarnings( "ConfusingMainMethod" )
- public static void main( final Arguments args ) {
- new HeadlessApp( args );
+ public static void run(
+ final Arguments args,
+ final PrintStream errStream ) {
+ final var app = new HeadlessApp( args, errStream );
+ app.run();
}
}
src/main/java/com/keenwrite/constants/Constants.java
* Prevent instantiation.
*/
- private Constants() {
- }
+ private Constants() {}
/**
src/main/java/com/keenwrite/editors/markdown/MarkdownEditor.java
import java.util.regex.Pattern;
-import static com.keenwrite.MainApp.keyDown;
+import static com.keenwrite.GuiApp.keyDown;
import static com.keenwrite.constants.Constants.*;
import static com.keenwrite.events.StatusEvent.clue;
src/main/java/com/keenwrite/security/PermissiveCertificate.java
* Use {@link #installTrustManager()}.
*/
- private PermissiveCertificate() {
- }
+ private PermissiveCertificate() {}
}
src/main/java/com/keenwrite/typesetting/installer/panes/ManagerOutputPane.java
*/
public abstract class ManagerOutputPane extends InstallerPane {
- private final static String PROP_EXECUTOR =
+ private static final String PROP_EXECUTOR =
ManagerOutputPane.class.getCanonicalName();
src/test/java/com/keenwrite/io/MediaTypeSnifferTest.java
final var actualExtension = valueFrom( media ).getExtension();
final var expectedExtension = getExtension( image.toString() );
- System.out.println( String.format( "%s%s", image, " -> \{media}" ) );
+ System.out.printf( "%s -> %s%n", image, media );
assertEquals( expectedExtension, actualExtension );

Reinstates stderr, suppresses slf4j, fixes test build error

Author DaveJarvis <email>
Date 2024-10-05 14:49:59 GMT-0700
Commit c2fb90a8d0c90e0d31b412476b9bdd105521de5b
Parent f005e9b
Delta 650 lines added, 615 lines removed, 35-line increase