[WIP] Read ImageJ.cfg #3

This commit is contained in:
Jarek Sacha 2023-06-05 21:02:57 -04:00
parent 60ad1b1816
commit faf6a879e5
No known key found for this signature in database
GPG key ID: F29625CE62288163
6 changed files with 159 additions and 2 deletions

View file

@ -0,0 +1,114 @@
/*
* Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved.
* Author's e-mail: jpsacha at gmail.com
*/
package ij_plugins.imagej_launcher
import os.Path
object IJConfigFile:
private val FileName: String = "ImageJ.cfg"
private val MagicHeader: String = "# ImageJ startup properties"
private val MaxHeapMBKey: String = "maxheap.mb"
private val JvmArgsKey: String = "jvmargs"
private val LegacyModeKey: String = "legacy.mode"
private val AssignmentSymbol: String = "="
private val CommentSymbol: String = "#"
/**
* Read `ImageJ.cfg` from a specified directory.
* The configuration file must be in ImageJ2 format (with magic header "# ImageJ startup properties").
*
* @param dir directory where `ImageJ.cfg` is located
* @param logger logger
* @return option containing the configuration file (Right).
* The option is empty if file is not present in input `dir`.
* An error message is returned if file is present but cannot be read without errors (Left).
*/
def readFromDir(dir: Path)(using logger: Logger): Either[String, Option[IJCConfig]] =
logger.debug(s"Reading ImageJ configuration from directory: $dir")
val path = dir / FileName
logger.debug(s" Looking for $FileName in: '$path'")
readFromFile(path)
private[imagej_launcher] def readFromFile(ijConfigFile: Path)(using logger: Logger): Either[String, Option[IJCConfig]] =
if os.exists(ijConfigFile) then
val lines = os.read.lines(ijConfigFile)
val cfgE =
lines.headOption match
case Some(line) =>
if line.trim.startsWith(MagicHeader) then
logger.debug(s" Parsing $FileName")
for
props <- parseProps(lines)
maxHeapMB <- parseLong(props, MaxHeapMBKey)
jvmArgs <- parseString(props, JvmArgsKey)
legacyMode <- parseBoolean(props, LegacyModeKey)
yield Option(IJCConfig(maxHeapMB, jvmArgs, legacyMode))
else
Left(s"$FileName is invalid, does not contain header: '$MagicHeader'")
case None =>
Left(s"$FileName is empty.")
// Add context to the error message, if there is one
cfgE.left.map(e => s"Failed to read $FileName. $e")
else
logger.debug(s" $FileName not found: $ijConfigFile")
Right(None)
private[imagej_launcher] def parseProps(lines: Seq[String]): Either[String, Map[String, String]] =
// Parse each line
val lineResults: Seq[Either[String, (String, String)]] =
lines
.zipWithIndex
.filter { case (line, _) => !line.startsWith(CommentSymbol) }
.map { case (line, lineNumber) =>
val index = line.indexOf(AssignmentSymbol)
if index > 0 then
val key = line.substring(0, index).trim
if !key.isBlank then
Right(key -> line.substring(index + 1).trim)
else
Left(s"Error in line $lineNumber: key cannot be empty: '$line'")
else if index == 0 then
Left(s"Error in line $lineNumber: line cannot start with an assignment symbol '$AssignmentSymbol': '$line'")
else
Left(s"Error in line $lineNumber: no assignment symbol '$AssignmentSymbol' present: '$line'")
}
// Separate lines with errors
val errors = lineResults.collect { case e: Left[?, ?] => e }
if errors.isEmpty then
// Convert key->value pairs to a map
val r =
lineResults
.collect { case e: Right[?, ?] => e }
.map(_.value)
.toMap
Right(r)
else
Left(errors.map(_.value).mkString("\n"))
private def parseLong(props: Map[String, String], key: String): Either[String, Long] =
parseString(props, key).flatMap: s =>
s.toLongOption match
case Some(v) => Right(v)
case None => Left(s"Failed to parse '$s' as an integer.")
private def parseString(props: Map[String, String], key: String): Either[String, String] =
props.get(key) match
case Some(v) => Right(v)
case None => Left(s"No key named '$key'.")
private def parseBoolean(props: Map[String, String], key: String): Either[String, Boolean] =
parseString(props, key).flatMap: s =>
s.toBooleanOption match
case Some(v) => Right(v)
case None => Left(s"Failed to parse '$s' as a boolean.")
/** Configuration loaded from `ImageJ.cfg`. */
case class IJCConfig(maxHeapMB: Long, jvmArgs: String, legacyMode: Boolean)
end IJConfigFile

View file

@ -13,7 +13,7 @@ import os.Path
import java.io.File
import java.lang.ProcessBuilder.Redirect
class Launcher(logger: Logger):
class Launcher(using logger: Logger):
def run(config: Config): Unit =
prepareLaunch(config) match
@ -30,6 +30,7 @@ class Launcher(logger: Logger):
for
ijDir <- IJDir.locate(config, logger)
_ <- Updater.update(ijDir, config.dryRun, logger)
ijConfig <- IJConfigFile.readFromDir(ijDir)
launcherJar <- findImageJLauncherJar(ijDir.toIO)
javaExe <- locateJavaExecutable(config, ijDir.toIO)
systemType <- determineSystemType()
@ -178,6 +179,7 @@ class Launcher(logger: Logger):
"plugins",
"net.imagej.Main"
)
end buildCommandLine
private def launch(command: Seq[String]): Unit =
logger.debug("launchImageJ ...")

View file

@ -69,7 +69,7 @@ object Main:
private def setupLogger(logLevel: Logger.Level): Unit = logger = new Logger(logLevel)
private def runLauncher(config: Config): Unit = new Launcher(logger).run(config)
private def runLauncher(config: Config): Unit = new Launcher(using logger).run(config)
case class Config(
logLevel: Logger.Level = Logger.Level.Error,

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved.
* Author's e-mail: jpsacha at gmail.com
*/
package ij_plugins.imagej_launcher
import org.scalatest.flatspec.AnyFlatSpec
class IJConfigFileTest extends AnyFlatSpec:
given logger:Logger = new Logger(Logger.Level.All)
"IJConfigFile" should "read valid config files" in:
val path = os.pwd / "test" / "data" / "config_valid" / "ImageJ.cfg"
IJConfigFile.readFromFile(path) match
case Right(cfg) =>
logger.debug(cfg.toString)
assert(cfg.nonEmpty)
case Left(err) =>
logger.debug(s"Unexpected error: $err")
fail()
it should "fail reading invalid config files" in:
val path = os.pwd / "test" / "data" / "config_invalid" / "ImageJ.cfg"
IJConfigFile.readFromFile(path) match
case Right(cfg) =>
logger.debug(cfg.toString)
fail(s"Expected to return errors when reading config, but read config as: $cfg")
case Left(err) =>
logger.debug(s"Expected error\n:$err")

View file

@ -0,0 +1,4 @@
# ImageJ startup properties
maxheap.mb = 1024m
jvmargs = -XX:+HeapDumpOnOutOfMemoryError -Xincgc
legacy.mode = false

View file

@ -0,0 +1,4 @@
# ImageJ startup properties
maxheap.mb = 1024
jvmargs = -XX:+HeapDumpOnOutOfMemoryError -Xincgc
legacy.mode = false