Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
src/main/java/com/keenwrite/events/StatusEvent.java
package com.keenwrite.events;
-import com.keenwrite.AppCommands;
-
import java.util.List;
*/
public final class StatusEvent implements AppEvent {
- /**
- * Reference a class in the top-level package that doesn't depend on any
- * JavaFX APIs.
- */
- private static final String PACKAGE_NAME = AppCommands.class.getPackageName();
-
private static final String ENGLISHIFY =
"(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])";
}
+ /**
+ * Constructs a new event that contains information about an unexpected issue.
+ *
+ * @param problem The issue encountered by the software, never {@code null}.
+ */
public StatusEvent( final Throwable problem ) {
- this( "", problem );
+ this( problem.getMessage(), problem );
}
/**
* @param message The human-readable message text.
* @param problem May be {@code null} if no exception was thrown.
*/
public StatusEvent( final String message, final Throwable problem ) {
- assert message != null;
- mMessage = message;
+ mMessage = message == null ? "" : message;
mProblem = problem;
}
stream( trace.getStackTrace() )
.takeWhile( StatusEvent::filter )
- .limit( 10 )
+ .limit( 15 )
.toList()
.forEach( e -> sb.append( e.toString() ).append( NEWLINE ) );
private static boolean filter( final StackTraceElement e ) {
final var clazz = e.getClassName();
- return !(clazz.contains( PACKAGE_NAME ) ||
- clazz.contains( "org.renjin." ) ||
+ return !(clazz.contains( "org.renjin." ) ||
clazz.contains( "sun." ) ||
clazz.contains( "flexmark." ) ||
- clazz.contains( "java." ));
+ clazz.contains( "java." )
+ );
}
src/main/java/com/keenwrite/io/CommandNotFoundException.java
package com.keenwrite.io;
-import java.io.File;
import java.io.FileNotFoundException;
public CommandNotFoundException( final String command ) {
super( command );
- }
-
- /**
- * Creates a new exception indicating that the given command could not be
- * found (or executed).
- *
- * @param file The binary file's command name that could not be run.
- */
- public CommandNotFoundException( final File file ) {
- this( file.getAbsolutePath() );
}
}
src/main/java/com/keenwrite/io/SysFile.java
package com.keenwrite.io;
+import org.jetbrains.annotations.NotNull;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
import java.util.Optional;
import java.util.function.Function;
-import java.util.regex.Pattern;
+import java.util.function.Predicate;
import static com.keenwrite.constants.Constants.USER_DIRECTORY;
+import static com.keenwrite.events.StatusEvent.clue;
+import static com.keenwrite.io.WindowsRegistry.pathsWindows;
import static com.keenwrite.util.DataTypeConverter.toHex;
import static java.lang.System.getenv;
import static java.nio.file.Files.isExecutable;
-import static java.util.regex.Pattern.compile;
import static java.util.regex.Pattern.quote;
import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
private static final String[] EXTENSIONS = new String[]
{"", ".exe", ".bat", ".cmd", ".msi", ".com"};
+
+ private static final String WHERE_COMMAND =
+ IS_OS_WINDOWS ? "where" : "which";
/**
* Number of bytes to read at a time when computing this file's checksum.
*/
private static final int BUFFER_SIZE = 16384;
-
- //@formatter:off
- private static final String SYS_KEY =
- "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
- private static final String USR_KEY =
- "HKEY_CURRENT_USER\\Environment";
- //@formatter:on
-
- /**
- * Regular expression pattern for matching %VARIABLE% names.
- */
- private static final String VAR_REGEX = "%.*?%";
- private static final Pattern VAR_PATTERN = compile( VAR_REGEX );
-
- private static final String REG_REGEX = "\\s*path\\s+REG_EXPAND_SZ\\s+(.*)";
- private static final Pattern REG_PATTERN = compile( REG_REGEX );
/**
/**
- * Answers whether the path returned from {@link #locate()} is an executable
- * that can be run using a {@link ProcessBuilder}.
+ * Answers whether an executable can be found that can be run using a
+ * {@link ProcessBuilder}.
+ *
+ * @return {@code true} if the executable is runnable.
*/
public boolean canRun() {
* the fully qualified path, otherwise an empty result.
*
- * @param map The mapping function of registry variable names to values.
- * @return The fully qualified {@link Path} to the executable filename
- * provided at construction time.
+ * @return Fully qualified path to the executable, if found.
*/
- public Optional<Path> locate( final Function<String, String> map ) {
+ public Optional<Path> locate() {
+ final var dirList = new ArrayList<String>();
+ final var paths = pathsSane();
+ int began = 0;
+ int ended;
+
+ while( (ended = paths.indexOf( pathSeparatorChar, began )) != -1 ) {
+ final var dir = paths.substring( began, ended );
+ began = ended + 1;
+
+ dirList.add( dir );
+ }
+
+ final var dirs = dirList.toArray( new String[]{} );
+ var path = locate( dirs, "Wizard.container.executable.path" );
+
+ if( path.isEmpty() ) {
+ clue();
+
+ try {
+ path = where();
+ } catch( final IOException ex ) {
+ clue( "Wizard.container.executable.which", ex );
+ }
+ }
+
+ return path.isPresent()
+ ? path
+ : locate( System::getenv,
+ IS_OS_WINDOWS
+ ? "Wizard.container.executable.registry"
+ : "Wizard.container.executable.path" );
+ }
+
+ private Optional<Path> locate( final String[] dirs, final String msg ) {
final var exe = getName();
- final var paths = paths( map ).split( quote( pathSeparator ) );
- for( final var path : paths ) {
- final var p = Path.of( path ).resolve( exe );
+ for( final var dir : dirs ) {
+ final var p = Path.of( dir ).resolve( exe );
for( final var extension : EXTENSIONS ) {
}
+ clue( msg );
return Optional.empty();
}
- /**
- * Convenience method that locates a binary executable file in the path
- * by using {@link System#getenv(String)} to retrieve environment variables
- * that are expanded when parsing the PATH.
- *
- * @see #locate(Function)
- */
- public Optional<Path> locate() {
- return locate( System::getenv );
- }
+ private Optional<Path> locate(
+ final Function<String, String> map, final String msg ) {
+ final var paths = paths( map ).split( quote( pathSeparator ) );
- /**
- * Provides {@code null}-safe machinery to get a file name.
- *
- * @param p The path to the file name to retrieve (may be {@code null}).
- * @return The file name or the empty string if the path is not found.
- */
- public static String getFileName( final Path p ) {
- return p == null ? "" : getPathFileName( p );
+ return locate( paths, msg );
}
/**
- * If the path doesn't exist right before typesetting, switch the path
- * to the user's home directory to increase the odds of the typesetter
- * succeeding. This could help, for example, if the images directory was
- * deleted or moved.
+ * Runs {@code where} or {@code which} to determine the fully qualified path
+ * to an executable.
*
- * @param path The path to verify existence.
- * @return The given path, if it exists, otherwise the user's home directory.
+ * @return The path to the executable for this file, if found.
+ * @throws IOException Could not determine the location of the command.
*/
- public static Path normalize( final Path path ) {
- assert path != null;
-
- return path.toFile().exists()
- ? path
- : USER_DIRECTORY.toPath();
- }
-
- private static String getPathFileName( final Path p ) {
- assert p != null;
-
- final var f = p.getFileName();
+ public Optional<Path> where() throws IOException {
+ // The "where" command on Windows will automatically add the extension.
+ final var args = new String[]{WHERE_COMMAND, getName()};
+ final var output = run( text -> true, args );
+ final var result = output.lines().findFirst();
- return f == null ? "" : f.toString();
+ return result.map( Path::of );
}
* @return The revised PATH variables as stored in the registry.
*/
- private String paths( final Function<String, String> map ) {
+ private static String paths( final Function<String, String> map ) {
return IS_OS_WINDOWS ? pathsWindows( map ) : pathsSane();
}
- private String pathsSane() {
- return getenv( "PATH" );
- }
+ /**
+ * Answers whether this file's SHA-256 checksum equals the given
+ * hexadecimal-encoded checksum string.
+ *
+ * @param hex The string to compare against the checksum for this file.
+ * @return {@code true} if the checksums match; {@code false} on any
+ * error or checksums don't match.
+ */
+ public boolean isChecksum( final String hex ) {
+ assert hex != null;
- @SuppressWarnings( "SpellCheckingInspection" )
- private String pathsWindows( final Function<String, String> map ) {
try {
- final var hklm = query( SYS_KEY );
- final var hkcu = query( USR_KEY );
+ return checksum( "SHA-256" ).equalsIgnoreCase( hex );
+ } catch( final Exception ex ) {
+ return false;
+ }
+ }
- return expand( hklm, map ) + pathSeparator + expand( hkcu, map );
- } catch( final IOException ex ) {
- // Return the PATH environment variable if the registry query fails.
- return pathsSane();
+ /**
+ * Returns the hash code for this file.
+ *
+ * @return The hex-encoded hash code for the file contents.
+ */
+ @SuppressWarnings( "SameParameterValue" )
+ private String checksum( final String algorithm )
+ throws NoSuchAlgorithmException, IOException {
+ final var digest = MessageDigest.getInstance( algorithm );
+
+ try( final var in = new FileInputStream( this ) ) {
+ final var bytes = new byte[ BUFFER_SIZE ];
+ int count;
+
+ while( (count = in.read( bytes )) != -1 ) {
+ digest.update( bytes, 0, count );
+ }
+
+ return toHex( digest.digest() );
}
}
/**
- * Queries a registry key PATH value.
+ * Runs a command and collects standard output into a buffer.
*
- * @param key The registry key name to look up.
- * @return The value for the registry key.
+ * @param filter Provides an injected test to determine whether the line
+ * read from the command's standard output is to be added to
+ * the result buffer.
+ * @param args The command and its arguments to run.
+ * @return The standard output from the command, filtered.
+ * @throws IOException Could not run the command.
*/
- private String query( final String key ) throws IOException {
- final var regVarName = "path";
- final var args = new String[]{"reg", "query", key, "/v", regVarName};
+ @NotNull
+ public static String run( final Predicate<String> filter,
+ final String[] args ) throws IOException {
final var process = Runtime.getRuntime().exec( args );
final var stream = process.getInputStream();
- final var regValue = new StringBuffer( 1024 );
+ final var stdout = new StringBuffer( 2048 );
StreamGobbler.gobble( stream, text -> {
- if( text.contains( regVarName ) ) {
- regValue.append( parseRegEntry( text ) );
+ if( filter.test( text ) ) {
+ stdout.append( WindowsRegistry.parseRegEntry( text ) );
}
} );
process.destroy();
}
-
- return regValue.toString();
- }
-
- String parseRegEntry( final String text ) {
- assert text != null;
- final var matcher = REG_PATTERN.matcher( text );
- return matcher.find() ? matcher.group( 1 ) : text.trim();
+ return stdout.toString();
}
/**
- * PATH environment variables returned from the registry have unexpanded
- * variables of the form %VARIABLE%. This method will expand those values,
- * if possible, from the environment. This will only perform a single
- * expansion, which should be adequate for most needs.
+ * Provides {@code null}-safe machinery to get a file name.
*
- * @param s The %VARIABLE%-encoded value to expand.
- * @return The given value with all encoded values expanded.
+ * @param p The path to the file name to retrieve (may be {@code null}).
+ * @return The file name or the empty string if the path is not found.
*/
- String expand( final String s, final Function<String, String> map ) {
- // Assigned to the unexpanded string, initially.
- String expanded = s;
-
- final var matcher = VAR_PATTERN.matcher( expanded );
-
- while( matcher.find() ) {
- final var match = matcher.group( 0 );
- String value = map.apply( match );
-
- if( value == null ) {
- value = "";
- }
- else {
- value = value.replace( "\\", "\\\\" );
- }
-
- final var subexpression = compile( quote( match ) );
- expanded = subexpression.matcher( expanded ).replaceAll( value );
- }
-
- return expanded;
+ public static String getFileName( final Path p ) {
+ return p == null ? "" : getPathFileName( p );
}
/**
- * Answers whether this file's SHA-256 checksum equals the given
- * hexadecimal-encoded checksum string.
+ * If the path doesn't exist right before typesetting, switch the path
+ * to the user's home directory to increase the odds of the typesetter
+ * succeeding. This could help, for example, if the images directory was
+ * deleted or moved.
*
- * @param hex The string to compare against the checksum for this file.
- * @return {@code true} if the checksums match; {@code false} on any
- * error or checksums don't match.
+ * @param path The path to verify existence.
+ * @return The given path, if it exists, otherwise the user's home directory.
*/
- public boolean isChecksum( final String hex ) {
- assert hex != null;
+ public static Path normalize( final Path path ) {
+ assert path != null;
- try {
- return checksum( "SHA-256" ).equalsIgnoreCase( hex );
- } catch( final Exception ex ) {
- return false;
- }
+ return path.toFile().exists() ? path : USER_DIRECTORY.toPath();
}
- /**
- * Returns the hash code for this file.
- *
- * @return The hex-encoded hash code for the file contents.
- */
- @SuppressWarnings( "SameParameterValue" )
- private String checksum( final String algorithm )
- throws NoSuchAlgorithmException, IOException {
- final var digest = MessageDigest.getInstance( algorithm );
+ private static String pathsSane() {
+ return getenv( "PATH" );
+ }
- try( final var in = new FileInputStream( this ) ) {
- final var bytes = new byte[ BUFFER_SIZE ];
- int count;
+ private static String getPathFileName( final Path p ) {
+ assert p != null;
- while( (count = in.read( bytes )) != -1 ) {
- digest.update( bytes, 0, count );
- }
+ final var f = p.getFileName();
- return toHex( digest.digest() );
- }
+ return f == null ? "" : f.toString();
}
}
src/main/java/com/keenwrite/io/WindowsRegistry.java
+/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. */
+package com.keenwrite.io;
+
+import java.io.IOException;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+import static java.io.File.pathSeparator;
+import static java.lang.System.getenv;
+import static java.util.regex.Pattern.compile;
+import static java.util.regex.Pattern.quote;
+
+/**
+ * Responsible for obtaining Windows registry key values.
+ */
+public class WindowsRegistry {
+ //@formatter:off
+ private static final String SYS_KEY =
+ "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
+ private static final String USR_KEY =
+ "HKEY_CURRENT_USER\\Environment";
+ //@formatter:on
+
+ /**
+ * Regular expression pattern for matching %VARIABLE% names.
+ */
+ private static final String VAR_REGEX = "%.*?%";
+ private static final Pattern VAR_PATTERN = compile( VAR_REGEX );
+
+ private static final String REG_REGEX = "\\s*path\\s+REG_EXPAND_SZ\\s+(.*)";
+ private static final Pattern REG_PATTERN = compile( REG_REGEX );
+
+ /**
+ * Returns the value of the Windows PATH registry key.
+ *
+ * @return The PATH environment variable if the registry query fails.
+ */
+ @SuppressWarnings( "SpellCheckingInspection" )
+ public static String pathsWindows( final Function<String, String> map ) {
+ try {
+ final var hklm = query( SYS_KEY );
+ final var hkcu = query( USR_KEY );
+
+ return expand( hklm, map ) + pathSeparator + expand( hkcu, map );
+ } catch( final IOException ex ) {
+ return getenv( "PATH" );
+ }
+ }
+
+ /**
+ * Queries a registry key PATH value.
+ *
+ * @param key The registry key name to look up.
+ * @return The value for the registry key.
+ */
+ private static String query( final String key ) throws IOException {
+ final var registryVarName = "path";
+ final var args = new String[]{"reg", "query", key, "/v", registryVarName};
+
+ return SysFile.run( text -> text.contains( registryVarName ), args );
+ }
+
+ static String parseRegEntry( final String text ) {
+ assert text != null;
+
+ final var matcher = REG_PATTERN.matcher( text );
+ return matcher.find() ? matcher.group( 1 ) : text.trim();
+ }
+
+ /**
+ * PATH environment variables returned from the registry have unexpanded
+ * variables of the form %VARIABLE%. This method will expand those values,
+ * if possible, from the environment. This will only perform a single
+ * expansion, which should be adequate for most needs.
+ *
+ * @param s The %VARIABLE%-encoded value to expand.
+ * @return The given value with all encoded values expanded.
+ */
+ static String expand( final String s, final Function<String, String> map ) {
+ // Assigned to the unexpanded string, initially.
+ String expanded = s;
+
+ final var matcher = VAR_PATTERN.matcher( expanded );
+
+ while( matcher.find() ) {
+ final var match = matcher.group( 0 );
+ String value = map.apply( match );
+
+ if( value == null ) {
+ value = "";
+ }
+ else {
+ value = value.replace( "\\", "\\\\" );
+ }
+
+ final var subexpression = compile( quote( match ) );
+ expanded = subexpression.matcher( expanded ).replaceAll( value );
+ }
+
+ return expanded;
+ }
+}
src/main/java/com/keenwrite/typesetting/GuestTypesetter.java
import java.util.concurrent.Callable;
+import static com.keenwrite.events.StatusEvent.clue;
import static com.keenwrite.io.StreamGobbler.gobble;
import static com.keenwrite.io.SysFile.normalize;
-import static com.keenwrite.typesetting.containerization.Podman.MANAGER;
import static java.lang.String.format;
*/
static boolean isReady() {
- if( MANAGER.canRun() ) {
+ if( Podman.canRun() ) {
final var exitCode = new StringBuilder();
final var manager = new Podman();
// If the typesetter ran with an exit code of 0, it is available.
return exitCode.indexOf( "0" ) == 0;
- } catch( final CommandNotFoundException ignored ) { }
+ } catch( final CommandNotFoundException ex ) {
+ clue( ex );
+ }
}
src/main/java/com/keenwrite/typesetting/containerization/Podman.java
import java.io.File;
-import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
-import java.util.NoSuchElementException;
import static com.keenwrite.Bootstrap.CONTAINER_VERSION;
import static com.keenwrite.events.StatusEvent.clue;
import static java.lang.String.format;
import static java.lang.String.join;
import static java.lang.System.arraycopy;
import static java.util.Arrays.copyOf;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
/**
* Provides facilities for interacting with a container environment.
*/
public final class Podman implements ContainerManager {
- public static final SysFile MANAGER = new SysFile( "podman" );
+ private static final String BINARY = "podman";
+ private static final Path BINARY_PATH =
+ Path.of(
+ format( IS_OS_WINDOWS
+ ? "C:\\Program Files\\RedHat\\Podman\\%s.exe"
+ : "/usr/bin/%s",
+ BINARY
+ )
+ );
+ private static final SysFile MANAGER = new SysFile( BINARY );
+
public static final String CONTAINER_SHORTNAME = "typesetter";
public static final String CONTAINER_NAME =
format( "%s:%s", CONTAINER_SHORTNAME, CONTAINER_VERSION );
private final List<String> mMountPoints = new LinkedList<>();
public Podman() { }
+
+ /**
+ * Answers whether the container is installed and runnable on the host.
+ *
+ * @return {@code true} if the container is available.
+ */
+ public static boolean canRun() {
+ try {
+ return getExecutable().toFile().isFile();
+ } catch( final Exception ex ) {
+ clue( "Wizard.container.executable.run.error", ex );
+
+ // If the binary couldn't be found, then indicate that it cannot run.
+ return false;
+ }
+ }
+
+ private static Path getExecutable() {
+ final var executable = Files.isExecutable( BINARY_PATH );
+
+ clue( "Wizard.container.executable.run.scan", BINARY_PATH, executable );
+
+ return executable
+ ? BINARY_PATH
+ : MANAGER.locate().orElseThrow();
+ }
@Override
throws CommandNotFoundException {
try {
- final var exe = MANAGER.locate();
- final var path = exe.orElseThrow();
- final var builder = processBuilder( path, args );
- final var process = builder.start();
+ final var path = getExecutable();
final var joined = join( ",", args );
clue( "Wizard.container.process.enter", path, joined );
+
+ final var builder = processBuilder( path, args );
+ final var process = builder.start();
processor.start( process.getInputStream() );
return wait( process );
- } catch( final NoSuchElementException |
- IOException |
- InterruptedException ex ) {
+ } catch( final Exception ex ) {
+ clue( ex );
throw new CommandNotFoundException( MANAGER.toString() );
}
src/main/java/com/keenwrite/typesetting/installer/WizardConstants.java
-/* Copyright 2022 White Magic Software, Ltd. -- All rights reserved. */
-package com.keenwrite.typesetting.installer;
-
-/**
- * Provides common constants across all panes.
- */
-public class WizardConstants {
-
-
- private WizardConstants() { }
-}
src/main/java/com/keenwrite/ui/actions/GuiCommands.java
*/
private void file_export_pdf( final boolean dir ) {
- final var workspace = getWorkspace();
- final var themes = workspace.getFile(
- KEY_TYPESET_CONTEXT_THEMES_PATH
- );
- final var theme = workspace.stringProperty(
- KEY_TYPESET_CONTEXT_THEME_SELECTION
- );
- final var chapters = workspace.stringProperty(
- KEY_TYPESET_CONTEXT_CHAPTERS
- );
- final var settings = ExportSettings
- .builder()
- .with( ExportSettings.Mutator::setTheme, theme )
- .with( ExportSettings.Mutator::setChapters, chapters )
- .build();
-
// Don't re-validate the typesetter installation each time. If the
// user mucks up the typesetter installation, it'll get caught the
// next time the application is started. Don't use |= because it
// won't short-circuit.
mCanTypeset = mCanTypeset || Typesetter.canRun();
if( mCanTypeset ) {
+ final var workspace = getWorkspace();
+ final var theme = workspace.stringProperty(
+ KEY_TYPESET_CONTEXT_THEME_SELECTION
+ );
+ final var chapters = workspace.stringProperty(
+ KEY_TYPESET_CONTEXT_CHAPTERS
+ );
+
+ final var settings = ExportSettings
+ .builder()
+ .with( ExportSettings.Mutator::setTheme, theme )
+ .with( ExportSettings.Mutator::setChapters, chapters )
+ .build();
+
+ final var themes = workspace.getFile(
+ KEY_TYPESET_CONTEXT_THEMES_PATH
+ );
+
// If the typesetter is installed, allow the user to select a theme. If
// the themes aren't installed, a status message will appear.
src/main/resources/com/keenwrite/messages.properties
Wizard.typesetter.themes.checksum=983b8d3c4051c6c002b8111ea786ebc83d5496ab5f8d734e96413c59304b2a75
-Wizard.container.install.command=Running ''{0}''
+Wizard.container.install.command=Installing container using: ''{0}''
Wizard.container.install.await=Waiting for installer to finish
Wizard.container.install.download.started=Download ''{0}'' started
Wizard.container.install.download.running=Download in progress, please wait
-Wizard.container.process.enter=Running ''{0}'' ''{1]''
-Wizard.container.process.exit=Exit code: {0}
+Wizard.container.process.enter=Running ''{0}'' ''{1}''
+Wizard.container.process.exit=Process exit code (zero means success): {0}
+Wizard.container.executable.run.scan=''{0}'' is executable: {1}
+Wizard.container.executable.run.error=Cannot run container
+Wizard.container.executable.which=Cannot find container using search command
+Wizard.container.executable.path=Cannot find container using PATH variable
+Wizard.container.executable.registry=Cannot find container using registry
# STEP 1: Introduction panel (all)
src/test/java/com/keenwrite/io/SysFileTest.java
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+
+import static org.junit.jupiter.api.Assertions.*;
class SysFileTest {
- private static final String REG_PATH_PREFIX =
- "%USERPROFILE%";
- private static final String REG_PATH_SUFFIX =
- "\\AppData\\Local\\Microsoft\\WindowsApps;";
- private static final String REG_PATH = REG_PATH_PREFIX + REG_PATH_SUFFIX;
@Test
void test_Locate_ExistingExecutable_PathFound() {
- final var command = "ls";
- final var file = new SysFile( command );
- assertTrue( file.canRun() );
-
- final var located = file.locate();
- assertTrue( located.isPresent() );
-
- final var path = located.get();
- final var actual = path.toAbsolutePath().toString();
- final var expected = "/usr/bin/" + command;
-
- assertEquals( expected, actual );
+ testFunction( SysFile::locate, "ls", "/usr/bin/ls" );
}
@Test
- void test_Parse_RegistryEntry_ValueObtained() {
- final var file = new SysFile( "unused" );
- final var expected = REG_PATH;
- final var actual =
- file.parseRegEntry( " path REG_EXPAND_SZ " + expected );
-
- assertEquals( expected, actual );
+ void test_Where_ExistingExecutable_PathFound() {
+ testFunction( sysFile -> {
+ try {
+ return sysFile.where();
+ } catch( final IOException e ) {
+ throw new RuntimeException( e );
+ }
+ }, "which", "/usr/bin/which" );
}
- @Test
- void test_Expand_RegistryEntry_VariablesExpanded() {
- final var value = "UserProfile";
- final var file = new SysFile( "unused" );
- final var expected = value + REG_PATH_SUFFIX;
- final var actual = file.expand( REG_PATH, s -> value );
+ void testFunction( final Function<SysFile, Optional<Path>> consumer,
+ final String command,
+ final String expected ) {
+ final var file = new SysFile( command );
+ final var path = consumer.apply( file );
+ final var failed = new AtomicBoolean( false );
- assertEquals( expected, actual );
+ assertTrue( file.canRun() );
+
+ path.ifPresentOrElse(
+ location -> {
+ final var actual = location.toAbsolutePath().toString();
+
+ assertEquals( expected, actual );
+ },
+ () -> failed.set( true )
+ );
+
+ assertFalse( failed.get() );
}
}
src/test/java/com/keenwrite/io/WindowsRegistryTest.java
+package com.keenwrite.io;
+
+import org.junit.jupiter.api.Test;
+
+import static com.keenwrite.io.WindowsRegistry.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+class WindowsRegistryTest {
+ private static final String REG_PATH_PREFIX =
+ "%USERPROFILE%";
+ private static final String REG_PATH_SUFFIX =
+ "\\AppData\\Local\\Microsoft\\WindowsApps;";
+ private static final String REG_PATH = REG_PATH_PREFIX + REG_PATH_SUFFIX;
+
+ @Test
+ void test_Parse_RegistryEntry_ValueObtained() {
+ final var expected = REG_PATH;
+ final var actual = parseRegEntry(
+ " path REG_EXPAND_SZ " + expected
+ );
+
+ assertEquals( expected, actual );
+ }
+
+ @Test
+ void test_Expand_RegistryEntry_VariablesExpanded() {
+ final var value = "UserProfile";
+ final var expected = value + REG_PATH_SUFFIX;
+ final var actual = expand( REG_PATH, s -> value );
+
+ assertEquals( expected, actual );
+ }
+}

Adds logging, fixes installer, upgrades ConTeXt, container, and themes

Author DaveJarvis <email>
Date 2023-07-19 22:16:30 GMT-0700
Commit 60426e928c131004eeee2998e4b5b27f390efc57
Parent 64cb1c8
Delta 392 lines added, 252 lines removed, 140-line increase