From 2a9ecc81e10345e93008d5ff916414c17b9bf9d9 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Mon, 12 Aug 2024 09:56:24 +0200 Subject: [PATCH 01/18] first commit --- .../scala/io/viash/wrapper/BashWrapper.scala | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index 0faefc48f..a0aa59eef 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -52,41 +52,48 @@ object BashWrapper { } def store(name: String, env: String, value: String, multiple_sep: Option[String]): Array[String] = { + // note: 'value' is split using multiple_sep into 'values' for backwards compatibility. + // todo: strip quotes and escape as suggested by https://github.com/viash-io/viash/issues/705#issuecomment-2208448576 if (multiple_sep.isDefined) { - s"""if [ -z "$$$env" ]; then - | $env=$value - |else - | $env="$$$env${multiple_sep.get}"$value - |fi""".stripMargin.split("\n") + // note: 'values' is a global variable here! + s"""if [ "$value" == "UNDEFINED" ]; then + | unset $value + |else + | readarray -d $$';' -t values <<< $value + | if [ -z "$$$env" ]; then + | $env=( "$${values[@]}" ) + | else + | $env+=( "$${values[@]}" ) + | fi + |fi""".stripMargin.split("\n") } else { - Array( - s"""[ -n "$$$env" ] && ViashError Bad arguments for option \\'$name\\': \\'$$$env\\' \\& \\'$$2\\' - you should provide exactly one argument for this option. && exit 1""", - env + "=" + value - ) + s"""if [ "$value" == "UNDEFINED" ]; then + | unset $value + |else + | [ -n "$$$env" ] && ViashError Bad arguments for option \\'$name\\': \\'$$$env\\' \\& \\'$$2\\' - you should provide exactly one argument for this option. && exit 1 + | $env=$value + |fi""".stripMargin.split("\n") } } def argStore( name: String, plainName: String, - store: String, + value: String, argsConsumed: Int, multiple_sep: Option[String] = None ): String = { - argsConsumed match { - case num if num > 1 => - s""" $name) - | ${this.store(name, plainName, store, multiple_sep).mkString("\n ")} - | [ $$# -lt $argsConsumed ] && ViashError Not enough arguments passed to $name. Use "--help" to get more information on the parameters. && exit 1 - | shift $argsConsumed - | ;;""".stripMargin - case _ => - s""" $name) - | ${this.store(name, plainName, store, multiple_sep).mkString("\n ")} - | shift $argsConsumed - | ;;""".stripMargin - } - + val argmatchError = + if (argsConsumed > 1) { + s"""\n [ $$# -lt $argsConsumed ] && ViashError Not enough arguments passed to $name. Use "--help" to get more information on the parameters. && exit 1""" + } else { + "" + } + + s""" $name)$argmatchError + | ${this.store(name, plainName, value, multiple_sep).mkString("\n ")} + | shift $argsConsumed + | ;;""".stripMargin } def argStoreSed(name: String, plainName: String, multiple_sep: Option[String] = None): String = { From 77cd691e75ed36cd0d677f12b812dae393c3808c Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Mon, 12 Aug 2024 10:29:07 +0200 Subject: [PATCH 02/18] fix usage of wrong varible --- src/main/scala/io/viash/wrapper/BashWrapper.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index a0aa59eef..7358c1f06 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -56,8 +56,8 @@ object BashWrapper { // todo: strip quotes and escape as suggested by https://github.com/viash-io/viash/issues/705#issuecomment-2208448576 if (multiple_sep.isDefined) { // note: 'values' is a global variable here! - s"""if [ "$value" == "UNDEFINED" ]; then - | unset $value + s"""if [ "$env" == "UNDEFINED" ]; then + | unset $env |else | readarray -d $$';' -t values <<< $value | if [ -z "$$$env" ]; then @@ -67,8 +67,8 @@ object BashWrapper { | fi |fi""".stripMargin.split("\n") } else { - s"""if [ "$value" == "UNDEFINED" ]; then - | unset $value + s"""if [ "$env" == "UNDEFINED" ]; then + | unset $env |else | [ -n "$$$env" ] && ViashError Bad arguments for option \\'$name\\': \\'$$$env\\' \\& \\'$$2\\' - you should provide exactly one argument for this option. && exit 1 | $env=$value From 8190a76f2009743a26f73ae04c4ccf9af806de04 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Mon, 12 Aug 2024 10:29:34 +0200 Subject: [PATCH 03/18] fix missing $ --- src/main/scala/io/viash/wrapper/BashWrapper.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index 7358c1f06..0d295b93d 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -56,7 +56,7 @@ object BashWrapper { // todo: strip quotes and escape as suggested by https://github.com/viash-io/viash/issues/705#issuecomment-2208448576 if (multiple_sep.isDefined) { // note: 'values' is a global variable here! - s"""if [ "$env" == "UNDEFINED" ]; then + s"""if [ "$$$env" == "UNDEFINED" ]; then | unset $env |else | readarray -d $$';' -t values <<< $value @@ -67,7 +67,7 @@ object BashWrapper { | fi |fi""".stripMargin.split("\n") } else { - s"""if [ "$env" == "UNDEFINED" ]; then + s"""if [ "$$$env" == "UNDEFINED" ]; then | unset $env |else | [ -n "$$$env" ] && ViashError Bad arguments for option \\'$name\\': \\'$$$env\\' \\& \\'$$2\\' - you should provide exactly one argument for this option. && exit 1 From a0b8c622a2a2f06bdbc7e845f5ea36388c24accb Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Mon, 12 Aug 2024 10:53:25 +0200 Subject: [PATCH 04/18] fix variables again --- src/main/scala/io/viash/wrapper/BashWrapper.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index 0d295b93d..b2eb1e9d9 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -55,11 +55,12 @@ object BashWrapper { // note: 'value' is split using multiple_sep into 'values' for backwards compatibility. // todo: strip quotes and escape as suggested by https://github.com/viash-io/viash/issues/705#issuecomment-2208448576 if (multiple_sep.isDefined) { - // note: 'values' is a global variable here! - s"""if [ "$$$env" == "UNDEFINED" ]; then + // note: 'value' and 'values' are global values!! + s"""value=$value + |if [ $$value == UNDEFINED ]; then | unset $env |else - | readarray -d $$';' -t values <<< $value + | readarray -d $$';' -t values <<< $$value | if [ -z "$$$env" ]; then | $env=( "$${values[@]}" ) | else @@ -67,11 +68,12 @@ object BashWrapper { | fi |fi""".stripMargin.split("\n") } else { - s"""if [ "$$$env" == "UNDEFINED" ]; then + s"""value=$value + |if [ $$value == UNDEFINED ]; then | unset $env |else | [ -n "$$$env" ] && ViashError Bad arguments for option \\'$name\\': \\'$$$env\\' \\& \\'$$2\\' - you should provide exactly one argument for this option. && exit 1 - | $env=$value + | $env=$$value |fi""".stripMargin.split("\n") } } From 8d96497854b712dc617c5aa36512499b20842cd3 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Mon, 12 Aug 2024 11:03:52 +0200 Subject: [PATCH 05/18] unset first, just in case --- src/main/scala/io/viash/wrapper/BashWrapper.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index b2eb1e9d9..1fb356271 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -56,7 +56,9 @@ object BashWrapper { // todo: strip quotes and escape as suggested by https://github.com/viash-io/viash/issues/705#issuecomment-2208448576 if (multiple_sep.isDefined) { // note: 'value' and 'values' are global values!! - s"""value=$value + // -> could rename them to `${env}_value` and `${env}_values` to avoid conflicts + s"""unset value values + |value=$value |if [ $$value == UNDEFINED ]; then | unset $env |else @@ -68,7 +70,8 @@ object BashWrapper { | fi |fi""".stripMargin.split("\n") } else { - s"""value=$value + s"""unset value + |value=$value |if [ $$value == UNDEFINED ]; then | unset $env |else From 3b96160794b1b56a00e56ce329f06a7db58d2c70 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Mon, 12 Aug 2024 11:08:03 +0200 Subject: [PATCH 06/18] replace `IFS=... for var in $myarg` with `for i in ${!myarg[@]}` --- .../io/viash/runners/ExecutableRunner.scala | 21 ++--- .../scala/io/viash/wrapper/BashWrapper.scala | 76 ++++++++++--------- 2 files changed, 44 insertions(+), 53 deletions(-) diff --git a/src/main/scala/io/viash/runners/ExecutableRunner.scala b/src/main/scala/io/viash/runners/ExecutableRunner.scala index 99f1fa3b8..53f3d87ba 100644 --- a/src/main/scala/io/viash/runners/ExecutableRunner.scala +++ b/src/main/scala/io/viash/runners/ExecutableRunner.scala @@ -395,20 +395,14 @@ final case class ExecutableRunner( val detectMounts = args.flatMap { case arg: FileArgument if arg.multiple => // resolve arguments with multiplicity different from singular args - val viash_temp = "VIASH_TEST_" + arg.plainName.toUpperCase() val chownIfOutput = if (arg.direction == Output) "\n VIASH_CHOWN_VARS+=( \"$var\" )" else "" Some( s""" |if [ ! -z "$$${arg.VIASH_PAR}" ]; then - | $viash_temp=() - | IFS='${Bash.escapeString(arg.multiple_sep, quote = true)}' - | for var in $$${arg.VIASH_PAR}; do - | unset IFS - | VIASH_DIRECTORY_MOUNTS+=( "$$(ViashDockerAutodetectMountArg "$$var")" ) - | var=$$(ViashDockerAutodetectMount "$$var") - | $viash_temp+=( "$$var" )$chownIfOutput + | for i in "$${!${arg.VIASH_PAR}[@]}"; do + | VIASH_DIRECTORY_MOUNTS+=( "$$(ViashDockerAutodetectMountArg $${${arg.VIASH_PAR}[$$i]})" ) + | ${arg.VIASH_PAR}[$$i]=$$(ViashDockerAutodetectMount $${${arg.VIASH_PAR}[$$i]}) | done - | ${arg.VIASH_PAR}=$$(IFS='${Bash.escapeString(arg.multiple_sep, quote = true)}' ; echo "$${$viash_temp[*]}") |fi""".stripMargin ) case arg: FileArgument => @@ -455,17 +449,12 @@ final case class ExecutableRunner( val stripAutomounts = args.flatMap { case arg: FileArgument if arg.multiple && arg.direction == Input => // resolve arguments with multiplicity different from singular args - val viash_temp = "VIASH_TEST_" + arg.plainName.toUpperCase() Some( s""" | if [ ! -z "$$${arg.VIASH_PAR}" ]; then - | unset $viash_temp - | IFS='${Bash.escapeString(arg.multiple_sep, quote = true)}' - | for var in $$${arg.VIASH_PAR}; do - | unset IFS - | ${BashWrapper.store("ViashDockerStripAutomount", viash_temp, "\"$(ViashDockerStripAutomount \"$var\")\"", Some(arg.multiple_sep)).mkString("\n ")} + | for i in "$${!${arg.VIASH_PAR}[@]}"; do + | ${arg.VIASH_PAR}[i]=$$(ViashDockerStripAutomount "$${${arg.VIASH_PAR}[i]}") | done - | ${arg.VIASH_PAR}="$$$viash_temp" | fi""".stripMargin ) case arg: FileArgument => diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index 1fb356271..e69d6e3a3 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -484,10 +484,8 @@ object BashWrapper { |fi""".stripMargin } else if (direction == Input) { s"""if [ ! -z "$$${param.VIASH_PAR}" ]; then - | IFS='${Bash.escapeString(param.multiple_sep, quote = true)}' | set -f - | for file in $$${param.VIASH_PAR}; do - | unset IFS + | for file in $${${param.VIASH_PAR}[@]}; do | if [ ! -e "$$file" ]; then | ViashError "$direction file '$$file' does not exist." | exit 1 @@ -520,10 +518,8 @@ object BashWrapper { createParentFiles.map { param => if (param.multiple && param.direction == Input) { s"""if [ ! -z "$$${param.VIASH_PAR}" ]; then - | IFS='${Bash.escapeString(param.multiple_sep, quote = true)}' | set -f - | for file in $$${param.VIASH_PAR}; do - | unset IFS + | for file in $${${param.VIASH_PAR}[@]}; do | if [ ! -d "$$(dirname "$$file")" ]; then | mkdir -p "$$(dirname "$$file")" | fi @@ -613,14 +609,12 @@ object BashWrapper { case param if param.multiple && param.direction == Input => val checkStart = s"""if [ -n "$$${param.VIASH_PAR}" ]; then - | IFS='${Bash.escapeString(param.multiple_sep, quote = true)}' | set -f - | for val in $$${param.VIASH_PAR}; do + | for val in $${${param.VIASH_PAR}[@]}; do |""" val checkEnd = s""" done | set +f - | unset IFS |fi |""".stripMargin // TODO add extra spaces for typeCheck, minCheck, maxCheck @@ -659,37 +653,47 @@ object BashWrapper { } def checkChoices[T](param: Argument[T], allowedChoices: List[T]) = { - val allowedChoicesString = allowedChoices.mkString(param.multiple_sep.toString) + val allowedChoicesString = allowedChoices.map("\"" + Bash.escapeString(_, quote = true) + "\"").mkString(" ") param match { case _ if param.multiple && param.direction == Input => s"""if [ ! -z "$$${param.VIASH_PAR}" ]; then - | ${param.VIASH_PAR}_CHOICES=("$allowedChoicesString") - | IFS='${Bash.escapeString(param.multiple_sep, quote = true)}' - | set -f - | for val in $$${param.VIASH_PAR}; do - | if ! [[ "${param.multiple_sep}$${${param.VIASH_PAR}_CHOICES[*]}${param.multiple_sep}" =~ "${param.multiple_sep}$${val}${param.multiple_sep}" ]]; then - | ViashError '${param.name}' specified value of \\'$${val}\\' is not in the list of allowed values. Use "--help" to get more information on the parameters. - | exit 1 - | fi - | done - | set +f - | unset IFS - |fi - |""".stripMargin + | ${param.VIASH_PAR}_CHOICES=($allowedChoicesString) + | set -f + | for val in $${${param.VIASH_PAR}}[@]}; do + | found=0 + | for choice in $${${param.VIASH_PAR}_CHOICES[@]}; do + | if [ "$$val" == "$$choice" ]; then + | found=1 + | break + | fi + | done + | if [ $$found -eq 0 ]; then + | ViashError '${param.name}' specified value of \\'$${val}\\' is not in the list of allowed values. Use "--help" to get more information on the parameters. + | exit 1 + | fi + | done + | set +f + |fi + |""".stripMargin case _ => s"""if [ ! -z "$$${param.VIASH_PAR}" ]; then - | ${param.VIASH_PAR}_CHOICES=("$allowedChoicesString") - | IFS='${Bash.escapeString(param.multiple_sep, quote = true)}' - | set -f - | if ! [[ "${param.multiple_sep}$${${param.VIASH_PAR}_CHOICES[*]}${param.multiple_sep}" =~ "${param.multiple_sep}$$${param.VIASH_PAR}${param.multiple_sep}" ]]; then - | ViashError '${param.name}' specified value of \\'$$${param.VIASH_PAR}\\' is not in the list of allowed values. Use "--help" to get more information on the parameters. - | exit 1 - | fi - | set +f - | unset IFS - |fi - |""".stripMargin + | ${param.VIASH_PAR}_CHOICES=($allowedChoicesString) + | found=0 + | for choice in $${${param.VIASH_PAR}_CHOICES[@]}; do + | if [ "$$${param.VIASH_PAR}" == "$$choice" ]; then + | found=1 + | break + | fi + | done + | set -f + | if [ $$found -eq 0 ]; then + | ViashError '${param.name}' specified value of \\'$$${param.VIASH_PAR}\\' is not in the list of allowed values. Use "--help" to get more information on the parameters. + | exit 1 + | fi + | set +f + |fi + |""".stripMargin } } val choicesCheckList = @@ -818,10 +822,8 @@ object BashWrapper { if (param.multiple && param.direction == Input) { s""" |if [ ! -z "$$${param.VIASH_PAR}" ]; then - | IFS='${Bash.escapeString(param.multiple_sep, quote = true)}' | set -f - | for val in $$${param.VIASH_PAR}; do - | unset IFS + | for val in $${${param.VIASH_PAR}[@]}; do | VIASH_EXECUTABLE_ARGS="$$VIASH_EXECUTABLE_ARGS$flag '$$val'" | done | set +f From 5aaa7dfe3a79018e7f987e367b401a0c03bc1c68 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Mon, 12 Aug 2024 11:33:43 +0200 Subject: [PATCH 07/18] fix type mismatch error --- src/main/scala/io/viash/wrapper/BashWrapper.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index e69d6e3a3..01a31d25a 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -653,7 +653,7 @@ object BashWrapper { } def checkChoices[T](param: Argument[T], allowedChoices: List[T]) = { - val allowedChoicesString = allowedChoices.map("\"" + Bash.escapeString(_, quote = true) + "\"").mkString(" ") + val allowedChoicesString = allowedChoices.map(choice => "\"" + Bash.escapeString(choice.toString, quote = true) + "\"").mkString(" ") param match { case _ if param.multiple && param.direction == Input => From df650b1ec36384fb162be08e7c104aec5039635b Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Mon, 12 Aug 2024 11:52:20 +0200 Subject: [PATCH 08/18] move dependencies to mods --- .../scala/io/viash/wrapper/BashWrapper.scala | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index 01a31d25a..eecc9f70e 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -233,29 +233,16 @@ object BashWrapper { case Some(_: Executable) => generateExecutableArgs(args) case _ => BashWrapperMods() } + val depMods = generateDependencies(config) // combine - val allMods = helpMods ++ parMods ++ mods ++ execMods ++ computationalRequirementMods + val allMods = helpMods ++ parMods ++ mods ++ execMods ++ computationalRequirementMods ++ depMods // generate header val header = Helper.generateScriptHeader(config) .map(h => Escaper(h, newline = true)) .mkString("# ", "\n# ", "") - val (localDependencies, remoteDependencies) = config.dependencies - .partition(d => d.isLocalDependency) - val localDependenciesStrings = localDependencies.map{ d => - // relativize the path of the main component to the local dependency - // TODO ideally we'd already have 'thisPath' precalculated but until that day, calculate it here - val thisPath = ViashNamespace.targetOutputPath("", "invalid_runner_name", config) - val relativePath = Paths.get(thisPath).relativize(Paths.get(d.configInfo.getOrElse("executable", ""))) - s"${d.VIASH_DEP}=\"$$VIASH_META_RESOURCES_DIR/$relativePath\"" - } - val remoteDependenciesStrings = remoteDependencies.map{ d => - s"${d.VIASH_DEP}=\"$$VIASH_TARGET_DIR/dependencies/${d.subOutputPath.get}/${Paths.get(d.configInfo.getOrElse("executable", "not_found")).getFileName()}\"" - } - val dependenciesStr = (localDependenciesStrings ++ remoteDependenciesStrings).mkString("\n") - /* GENERATE BASH SCRIPT */ s"""#!/usr/bin/env bash | @@ -334,9 +321,6 @@ object BashWrapper { |eval set -- $$VIASH_POSITIONAL_ARGS |${spaceCode(allMods.postParse)}${spaceCode(allMods.preRun)} | - |# set dependency paths - |$dependenciesStr - | |ViashDebug "Running command: ${executor.replaceAll("^eval (.*)", "\\$(echo $1)")}" |$heredocStart$executor${escapePipes(executionCode)}$heredocEnd |${spaceCode(allMods.postRun)}${spaceCode(allMods.last)} @@ -841,4 +825,31 @@ object BashWrapper { ) } + def generateDependencies( + config: Config + ): BashWrapperMods = { + if (config.dependencies.isEmpty) { + return BashWrapperMods() + } + + val (localDependencies, remoteDependencies) = config.dependencies + .partition(d => d.isLocalDependency) + + val localDependenciesStrings = localDependencies.map{ d => + // relativize the path of the main component to the local dependency + // TODO ideally we'd already have 'thisPath' precalculated but until that day, calculate it here + val thisPath = ViashNamespace.targetOutputPath("", "invalid_runner_name", config) + val relativePath = Paths.get(thisPath).relativize(Paths.get(d.configInfo.getOrElse("executable", ""))) + s"${d.VIASH_DEP}=\"$$VIASH_META_RESOURCES_DIR/$relativePath\"" + } + val remoteDependenciesStrings = remoteDependencies.map{ d => + s"${d.VIASH_DEP}=\"$$VIASH_TARGET_DIR/dependencies/${d.subOutputPath.get}/${Paths.get(d.configInfo.getOrElse("executable", "not_found")).getFileName()}\"" + } + val dependenciesStr = (localDependenciesStrings ++ remoteDependenciesStrings).mkString("\n") + + BashWrapperMods( + preRun = "\n# set dependency paths\n" + dependenciesStr + ) + } + } From 2b81cc95caf661f699f740c99c75ed07754404e7 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Mon, 12 Aug 2024 17:20:11 +0200 Subject: [PATCH 09/18] wip helper functions --- .../helpers/bashutils/ViashRenderYaml.sh | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh diff --git a/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh b/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh new file mode 100644 index 000000000..d2599a904 --- /dev/null +++ b/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh @@ -0,0 +1,82 @@ + + +function ViashRenderQuotedValue { + local key="$1" + local value="$2" + if [ "$value" == "UNDEFINED_ITEM" ]; then + echo "null" + return + fi + # escape quotes, backslashes and newlines, and then surround by quotes + echo "$value" | sed 's#"#\\"#g' | sed 's#\\#\\\\#g' | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's#^#"#g' | sed 's#$#"#g' +} + +function ViashRenderBooleanValue { + local key="$1" + local value="$2" + if [ "$value" == "UNDEFINED_ITEM" ]; then + echo "null" + return + fi + value=$(echo "$value" | tr '[:upper:]' '[:lower:]') + if [[ "$value" == "true" || "$value" == "yes" ]]; then + echo "true" + elif [[ "$value" == "false" || "$value" == "no" ]]; then + echo "false" + else + echo "Argument '$key' has to be a boolean, but got '$value'. Use '--help' to get more information on the parameters." + fi +} + +# can be infinite too +function ViashRenderUnquotedValue { + local key="$1" + local value="$2" + if [ "$value" == "UNDEFINED_ITEM" ]; then + echo "null" + return + fi + echo "$value" +} + +function ViashRenderYamlKeyValue { + local key="$1" + local type="$2" + local multiple="$3" + shift 3 + + local out="$key: " + + if [ "$1" == "UNDEFINED" ]; then + out+="null" + echo $out + return + fi + + if [ "$multiple" == "true" ]; then + out+="[" + fi + + first_elem=1 + + for value in "$@"; do + if [ $first_elem -eq 1 ]; then + first_elem=0 + else + out+=", " + fi + if [[ "$type" == "string" || "$type" == "file" ]]; then + out+="$(ViashRenderQuotedValue "$key" "$value")" + elif [[ "$type" == "boolean" || "$type" == "boolean_true" || "$type" == "boolean_false" ]]; then + out+="$(ViashRenderBooleanValue "$key" "$value")" + else + out+="$(ViashRenderUnquotedValue "$key" "$value")" + fi + done + + if [ "$multiple" == "true" ]; then + out+="]" + fi + + echo "$out" +} From af2bf7f0ca8ab87efc4a93cfd93415e882b4b341 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 13 Aug 2024 08:57:42 +0200 Subject: [PATCH 10/18] rename helper functions --- .../io/viash/helpers/bashutils/ViashRenderYaml.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh b/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh index d2599a904..639a9aae7 100644 --- a/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh +++ b/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh @@ -1,6 +1,6 @@ -function ViashRenderQuotedValue { +function ViashRenderYamlQuotedValue { local key="$1" local value="$2" if [ "$value" == "UNDEFINED_ITEM" ]; then @@ -11,7 +11,7 @@ function ViashRenderQuotedValue { echo "$value" | sed 's#"#\\"#g' | sed 's#\\#\\\\#g' | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's#^#"#g' | sed 's#$#"#g' } -function ViashRenderBooleanValue { +function ViashRenderYamlBooleanValue { local key="$1" local value="$2" if [ "$value" == "UNDEFINED_ITEM" ]; then @@ -29,7 +29,7 @@ function ViashRenderBooleanValue { } # can be infinite too -function ViashRenderUnquotedValue { +function ViashRenderYamlUnquotedValue { local key="$1" local value="$2" if [ "$value" == "UNDEFINED_ITEM" ]; then @@ -66,11 +66,11 @@ function ViashRenderYamlKeyValue { out+=", " fi if [[ "$type" == "string" || "$type" == "file" ]]; then - out+="$(ViashRenderQuotedValue "$key" "$value")" + out+="$(ViashRenderYamlQuotedValue "$key" "$value")" elif [[ "$type" == "boolean" || "$type" == "boolean_true" || "$type" == "boolean_false" ]]; then - out+="$(ViashRenderBooleanValue "$key" "$value")" + out+="$(ViashRenderYamlBooleanValue "$key" "$value")" else - out+="$(ViashRenderUnquotedValue "$key" "$value")" + out+="$(ViashRenderYamlUnquotedValue "$key" "$value")" fi done From 140d2f9f61d5d09e1ce617748e1ea8158d3da358 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 13 Aug 2024 08:57:53 +0200 Subject: [PATCH 11/18] fixes to generated code --- src/main/scala/io/viash/wrapper/BashWrapper.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index eecc9f70e..75df0549d 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -59,10 +59,10 @@ object BashWrapper { // -> could rename them to `${env}_value` and `${env}_values` to avoid conflicts s"""unset value values |value=$value - |if [ $$value == UNDEFINED ]; then + |if [ "$$value" == UNDEFINED ]; then | unset $env |else - | readarray -d $$';' -t values <<< $$value + | readarray -d ';' -t values < <(printf '%s' "$$value") | if [ -z "$$$env" ]; then | $env=( "$${values[@]}" ) | else @@ -72,7 +72,7 @@ object BashWrapper { } else { s"""unset value |value=$value - |if [ $$value == UNDEFINED ]; then + |if [ "$$value" == UNDEFINED ]; then | unset $env |else | [ -n "$$$env" ] && ViashError Bad arguments for option \\'$name\\': \\'$$$env\\' \\& \\'$$2\\' - you should provide exactly one argument for this option. && exit 1 From b47957a46a97e7eb45e0ab55d21bd961272f145e Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 13 Aug 2024 09:37:18 +0200 Subject: [PATCH 12/18] add more comments --- .../scala/io/viash/wrapper/BashWrapper.scala | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index 75df0549d..0d4a44bb0 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -51,33 +51,34 @@ object BashWrapper { ) } + // TODO: if argument is required, do not check for undefined + // TODO: if value is quoted, don't split by separator + // TODO: if the separator is escaped, don't split def store(name: String, env: String, value: String, multiple_sep: Option[String]): Array[String] = { // note: 'value' is split using multiple_sep into 'values' for backwards compatibility. // todo: strip quotes and escape as suggested by https://github.com/viash-io/viash/issues/705#issuecomment-2208448576 if (multiple_sep.isDefined) { // note: 'value' and 'values' are global values!! // -> could rename them to `${env}_value` and `${env}_values` to avoid conflicts - s"""unset value values - |value=$value - |if [ "$$value" == UNDEFINED ]; then - | unset $env - |else - | readarray -d ';' -t values < <(printf '%s' "$$value") - | if [ -z "$$$env" ]; then - | $env=( "$${values[@]}" ) - | else - | $env+=( "$${values[@]}" ) - | fi - |fi""".stripMargin.split("\n") + s"""value=$value + |if [ "$$value" == UNDEFINED ]; then + | unset $env + |else + | readarray -d '${multiple_sep.get}' -t values < <(printf '%s' "$$value") + | if [ -z "$$$env" ]; then + | $env=( "$${values[@]}" ) + | else + | $env+=( "$${values[@]}" ) + | fi + |fi""".stripMargin.split("\n") } else { - s"""unset value - |value=$value - |if [ "$$value" == UNDEFINED ]; then - | unset $env - |else - | [ -n "$$$env" ] && ViashError Bad arguments for option \\'$name\\': \\'$$$env\\' \\& \\'$$2\\' - you should provide exactly one argument for this option. && exit 1 - | $env=$$value - |fi""".stripMargin.split("\n") + s"""value=$value + |if [ "$$value" == UNDEFINED ]; then + | unset $env + |else + | [ -n "$$$env" ] && ViashError Bad arguments for option \\'$name\\': \\'$$$env\\' \\& \\'$$2\\' - you should provide exactly one argument for this option. && exit 1 + | $env=$$value + |fi""".stripMargin.split("\n") } } From 4e016be07188dcbf039c0b8e02448b051831bbec Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 13 Aug 2024 09:57:47 +0200 Subject: [PATCH 13/18] remove unused code --- src/main/scala/io/viash/helpers/Bash.scala | 29 ---------------------- 1 file changed, 29 deletions(-) diff --git a/src/main/scala/io/viash/helpers/Bash.scala b/src/main/scala/io/viash/helpers/Bash.scala index aa472adea..fc8b3d1c7 100644 --- a/src/main/scala/io/viash/helpers/Bash.scala +++ b/src/main/scala/io/viash/helpers/Bash.scala @@ -37,35 +37,6 @@ object Bash { lazy val ViashDockerFuns: String = readUtils("ViashDockerFuns") lazy val ViashLogging: String = readUtils("ViashLogging") - def save(saveVariable: String, args: Seq[String]): String = { - saveVariable + "=\"$" + saveVariable + " " + args.mkString(" ") + "\"" - } - - // generate strings in the form of: - // SAVEVARIABLE="$SAVEVARIABLE $(Quote arg1) $(Quote arg2)" - def quoteSave(saveVariable: String, args: Seq[String]): String = { - saveVariable + "=\"$" + saveVariable + - args.map(" $(ViashQuote \"" + _ + "\")").mkString + - "\"" - } - - def argStore(name: String, plainName: String, store: String, argsConsumed: Int, storeUnparsed: Option[String]): String = { - val passStr = - if (storeUnparsed.isDefined) { - "\n " + quoteSave(storeUnparsed.get, (1 to argsConsumed).map("$" + _)) - } else { - "" - } - s""" $name) - | $plainName=$store$passStr - | shift $argsConsumed - | ;;""".stripMargin - } - - def argStoreSed(name: String, plainName: String, storeUnparsed: Option[String]): String = { - argStore(name + "=*", plainName, "$(ViashRemoveFlags \"$1\")", 1, storeUnparsed) - } - /** * Access the parameters contents in a bash script, * taking into account that some characters need to be escaped. From 6ffee9372cbfba8f7e11e4a813a55336a15bb680 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Tue, 13 Aug 2024 09:58:06 +0200 Subject: [PATCH 14/18] make certain BashWrapper functions private --- src/main/scala/io/viash/wrapper/BashWrapper.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index 0d4a44bb0..e6154f97c 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -54,7 +54,7 @@ object BashWrapper { // TODO: if argument is required, do not check for undefined // TODO: if value is quoted, don't split by separator // TODO: if the separator is escaped, don't split - def store(name: String, env: String, value: String, multiple_sep: Option[String]): Array[String] = { + private def store(name: String, env: String, value: String, multiple_sep: Option[String]): Array[String] = { // note: 'value' is split using multiple_sep into 'values' for backwards compatibility. // todo: strip quotes and escape as suggested by https://github.com/viash-io/viash/issues/705#issuecomment-2208448576 if (multiple_sep.isDefined) { @@ -82,7 +82,7 @@ object BashWrapper { } } - def argStore( + private def argStore( name: String, plainName: String, value: String, @@ -102,11 +102,11 @@ object BashWrapper { | ;;""".stripMargin } - def argStoreSed(name: String, plainName: String, multiple_sep: Option[String] = None): String = { + private def argStoreSed(name: String, plainName: String, multiple_sep: Option[String] = None): String = { argStore(name + "=*", plainName, "$(ViashRemoveFlags \"$1\")", 1, multiple_sep) } - def spaceCode(str: String): String = { + private def spaceCode(str: String): String = { if (str != "") { "\n" + str + "\n" } else { @@ -826,7 +826,7 @@ object BashWrapper { ) } - def generateDependencies( + private def generateDependencies( config: Config ): BashWrapperMods = { if (config.dependencies.isEmpty) { From 147290d78bf808c21fbbd29a3e009f6abbfd1fa0 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Wed, 14 Aug 2024 09:37:47 +0200 Subject: [PATCH 15/18] improve yaml rendering --- .../bashutils/ViashParseArgumentValue.sh | 34 +++ .../helpers/bashutils/ViashRenderYaml.sh | 31 +- src/main/scala/io/viash/helpers/Bash.scala | 3 +- .../scala/io/viash/wrapper/BashWrapper.scala | 270 ++++++++++++------ 4 files changed, 234 insertions(+), 104 deletions(-) create mode 100644 src/main/resources/io/viash/helpers/bashutils/ViashParseArgumentValue.sh diff --git a/src/main/resources/io/viash/helpers/bashutils/ViashParseArgumentValue.sh b/src/main/resources/io/viash/helpers/bashutils/ViashParseArgumentValue.sh new file mode 100644 index 000000000..ea66225bb --- /dev/null +++ b/src/main/resources/io/viash/helpers/bashutils/ViashParseArgumentValue.sh @@ -0,0 +1,34 @@ +function ViashParseArgumentValue { + local env_name="$1" + local multiple="$2" + local flag="$3" + local value="$4" + + if [ $# -lt 4 ]; then + ViashError "Not enough arguments passed to ${flag}. Use '--help' to get more information on the parameters." + exit 1 + fi + + if [ "$multiple" == "false" ]; then + # check whether the variable is already set + if [ ! -z ${!env_name+x} ]; then + local -n prev_value="$env_name" + ViashError "Pass only one argument to argument '${flag}'. Found: ${prev_value@Q} & ${value@Q}" + exit 1 + fi + + # set the variable + declare -g "$env_name=${value}" + else + local new_values + + local -n prev_values="$env_name" + + # todo: allow escaping the delimiter + readarray -d ';' -t new_values < <(printf '%s' "$value") + + combined_values=( "${prev_values[@]}" "${new_values[@]}" ) + + declare -g -a "$env_name=(\"\${combined_values[@]}\")" + fi +} diff --git a/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh b/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh index 639a9aae7..e1a2eb3bd 100644 --- a/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh +++ b/src/main/resources/io/viash/helpers/bashutils/ViashRenderYaml.sh @@ -1,23 +1,18 @@ - - function ViashRenderYamlQuotedValue { local key="$1" local value="$2" - if [ "$value" == "UNDEFINED_ITEM" ]; then - echo "null" - return - fi # escape quotes, backslashes and newlines, and then surround by quotes - echo "$value" | sed 's#"#\\"#g' | sed 's#\\#\\\\#g' | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's#^#"#g' | sed 's#$#"#g' + echo "$value" | \ + sed 's#\\#\\\\#g' | \ + sed 's#"#\\"#g' | \ + sed ':a;N;$!ba;s/\n/\\n/g' | \ + sed 's#^#"#g;s#$#"#g' } function ViashRenderYamlBooleanValue { local key="$1" local value="$2" - if [ "$value" == "UNDEFINED_ITEM" ]; then - echo "null" - return - fi + # convert to lowercase value=$(echo "$value" | tr '[:upper:]' '[:lower:]') if [[ "$value" == "true" || "$value" == "yes" ]]; then echo "true" @@ -32,10 +27,6 @@ function ViashRenderYamlBooleanValue { function ViashRenderYamlUnquotedValue { local key="$1" local value="$2" - if [ "$value" == "UNDEFINED_ITEM" ]; then - echo "null" - return - fi echo "$value" } @@ -44,12 +35,12 @@ function ViashRenderYamlKeyValue { local type="$2" local multiple="$3" shift 3 - - local out="$key: " + + local out=" ${key}: " if [ "$1" == "UNDEFINED" ]; then out+="null" - echo $out + echo "$out" return fi @@ -65,7 +56,9 @@ function ViashRenderYamlKeyValue { else out+=", " fi - if [[ "$type" == "string" || "$type" == "file" ]]; then + if [ "$value" == "UNDEFINED_ITEM" ]; then + out+="null" + elif [[ "$type" == "string" || "$type" == "file" ]]; then out+="$(ViashRenderYamlQuotedValue "$key" "$value")" elif [[ "$type" == "boolean" || "$type" == "boolean_true" || "$type" == "boolean_false" ]]; then out+="$(ViashRenderYamlBooleanValue "$key" "$value")" diff --git a/src/main/scala/io/viash/helpers/Bash.scala b/src/main/scala/io/viash/helpers/Bash.scala index fc8b3d1c7..ad9753810 100644 --- a/src/main/scala/io/viash/helpers/Bash.scala +++ b/src/main/scala/io/viash/helpers/Bash.scala @@ -30,12 +30,11 @@ object Bash { lazy val ViashRemoveFlags: String = readUtils("ViashRemoveFlags") lazy val ViashAbsolutePath: String = readUtils("ViashAbsolutePath") lazy val ViashDockerAutodetectMount: String = readUtils("ViashDockerAutodetectMount") - // backwards compatibility, for now - lazy val ViashAutodetectMount: String = ViashDockerAutodetectMount.replace("ViashDocker", "Viash") lazy val ViashSourceDir: String = readUtils("ViashSourceDir") lazy val ViashFindTargetDir: String = readUtils("ViashFindTargetDir") lazy val ViashDockerFuns: String = readUtils("ViashDockerFuns") lazy val ViashLogging: String = readUtils("ViashLogging") + lazy val ViashParseArgumentValue: String = readUtils("ViashParseArgumentValue") /** * Access the parameters contents in a bash script, diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index e6154f97c..1093c1ff6 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -51,59 +51,83 @@ object BashWrapper { ) } - // TODO: if argument is required, do not check for undefined - // TODO: if value is quoted, don't split by separator - // TODO: if the separator is escaped, don't split - private def store(name: String, env: String, value: String, multiple_sep: Option[String]): Array[String] = { - // note: 'value' is split using multiple_sep into 'values' for backwards compatibility. - // todo: strip quotes and escape as suggested by https://github.com/viash-io/viash/issues/705#issuecomment-2208448576 - if (multiple_sep.isDefined) { - // note: 'value' and 'values' are global values!! - // -> could rename them to `${env}_value` and `${env}_values` to avoid conflicts - s"""value=$value - |if [ "$$value" == UNDEFINED ]; then - | unset $env - |else - | readarray -d '${multiple_sep.get}' -t values < <(printf '%s' "$$value") - | if [ -z "$$$env" ]; then - | $env=( "$${values[@]}" ) - | else - | $env+=( "$${values[@]}" ) - | fi - |fi""".stripMargin.split("\n") - } else { - s"""value=$value - |if [ "$$value" == UNDEFINED ]; then - | unset $env - |else - | [ -n "$$$env" ] && ViashError Bad arguments for option \\'$name\\': \\'$$$env\\' \\& \\'$$2\\' - you should provide exactly one argument for this option. && exit 1 - | $env=$$value - |fi""".stripMargin.split("\n") - } - } - - private def argStore( - name: String, - plainName: String, + /** + * Generate a flag parser for arguments of the form --arg value (by default) + * + * @param argName The name of the argument. + * @param envName The name of the environment variable to store the value in. + * @param value Where the value of the argument is stored during parsing. + * @param argsConsumed The number of arguments consumed by this argument. + * @param matchKey The key to match the argument with. + * @param multiple_sep The separator to use when splitting the value into multiple values. + */ + private def generateParser( + matchKey: String, + argName: String, + envName: String, value: String, argsConsumed: Int, multiple_sep: Option[String] = None ): String = { - val argmatchError = - if (argsConsumed > 1) { - s"""\n [ $$# -lt $argsConsumed ] && ViashError Not enough arguments passed to $name. Use "--help" to get more information on the parameters. && exit 1""" - } else { - "" - } - s""" $name)$argmatchError - | ${this.store(name, plainName, value, multiple_sep).mkString("\n ")} - | shift $argsConsumed + s""" ${matchKey}) + | ViashParseArgumentValue "${argName}" "${envName}" "${multiple_sep.isDefined}" "${value}" + | shift ${argsConsumed} | ;;""".stripMargin } - private def argStoreSed(name: String, plainName: String, multiple_sep: Option[String] = None): String = { - argStore(name + "=*", plainName, "$(ViashRemoveFlags \"$1\")", 1, multiple_sep) + /** + * Helper function for generating a flag parser for arguments of the form --arg ... + */ + private def generateFlagParser( + argName: String, + envName: String, + multiple_sep: Option[String] = None + ): String = { + generateParser( + argName = argName, + envName = envName, + matchKey = argName, + value = "$2", + argsConsumed = 2, + multiple_sep = multiple_sep + ) + } + + /** + * Helper function for generating a flag parser for arguments of the form --arg=... + */ + private def generateFlagWithEqualsParser( + argName: String, + envName: String, + multiple_sep: Option[String] = None + ): String = { + generateParser( + argName = argName, + matchKey = argName + "=*", + envName = envName, + value = "$(ViashRemoveFlags \"$1\")", + argsConsumed = 1, + multiple_sep = multiple_sep + ) + } + + /** + * Helper function for generating a flag parser for boolean arguments of the form --arg + */ + private def generateBooleanFlagParser( + argName: String, + envName: String, + value: Boolean + ): String = { + generateParser( + argName = argName, + envName = envName, + matchKey = argName, + value = value.toString, + argsConsumed = 1, + multiple_sep = None + ) } private def spaceCode(str: String): String = { @@ -228,16 +252,17 @@ object BashWrapper { // generate script modifiers val helpMods = generateHelp(config) - val computationalRequirementMods = generateComputationalRequirements(config) + val reqMods = generateComputationalRequirements(config) val parMods = generateParsers(args) val execMods = mainResource match { case Some(_: Executable) => generateExecutableArgs(args) case _ => BashWrapperMods() } val depMods = generateDependencies(config) + val workDirMods = generateWorkDir(argsMetaAndDeps) // combine - val allMods = helpMods ++ parMods ++ mods ++ execMods ++ computationalRequirementMods ++ depMods + val allMods = helpMods ++ parMods ++ mods ++ execMods ++ reqMods ++ depMods ++ workDirMods // generate header val header = Helper.generateScriptHeader(config) @@ -268,6 +293,7 @@ object BashWrapper { |${Bash.ViashSourceDir} |${Bash.ViashFindTargetDir} |${Bash.ViashLogging} + |${Bash.ViashParseArgumentValue} | |# find source folder of this component |VIASH_META_RESOURCES_DIR=`ViashSourceDir $${BASH_SOURCE[0]}` @@ -282,9 +308,12 @@ object BashWrapper { |VIASH_META_CONFIG="$$VIASH_META_RESOURCES_DIR/${ConfigMeta.metaFilename}" |VIASH_META_TEMP_DIR="$$VIASH_TEMP" | + |# preparse bashwrapper mods start ------------------------------ |${spaceCode(allMods.preParse)} + |# preparse bashwrapper mods end -------------------------------- + | |# initialise array - |VIASH_POSITIONAL_ARGS='' + |VIASH_POSITIONAL_ARGS=() | |while [[ $$# -gt 0 ]]; do | case "$$1" in @@ -309,22 +338,36 @@ object BashWrapper { | exit | ;; |${allMods.parsers} - | *) # positional arg or unknown option - | # since the positional args will be eval'd, can we always quote, instead of using ViashQuote - | VIASH_POSITIONAL_ARGS="$$VIASH_POSITIONAL_ARGS '$$1'" - | [[ $$1 == -* ]] && ViashWarning $$1 looks like a parameter but is not a defined parameter and will instead be treated as a positional argument. Use "--help" to get more information on the parameters. + | *) + | # positional arg or unknown option + | VIASH_POSITIONAL_ARGS+=("$$1") + | [[ $$1 == -* ]] && ViashWarning "Value '$$1' looks like a parameter but is not a defined parameter and will instead be treated as a positional argument. Use \\"--help\\" to get more information on the parameters." | shift # past argument | ;; | esac |done | |# parse positional parameters - |eval set -- $$VIASH_POSITIONAL_ARGS - |${spaceCode(allMods.postParse)}${spaceCode(allMods.preRun)} + |set -- "$${VIASH_POSITIONAL_ARGS[@]}" + | + |# postparse bashwrapper mods start ----------------------------- + |${spaceCode(allMods.postParse)} + |# postparse bashwrapper mods end ------------------------------- + | + |# prerun bashwrapper mods start -------------------------------- + |${spaceCode(allMods.preRun)} + |# prerun bashwrapper mods end ---------------------------------- | |ViashDebug "Running command: ${executor.replaceAll("^eval (.*)", "\\$(echo $1)")}" |$heredocStart$executor${escapePipes(executionCode)}$heredocEnd - |${spaceCode(allMods.postRun)}${spaceCode(allMods.last)} + | + |# postrun bashwrapper mods start ------------------------------- + |${spaceCode(allMods.postRun)} + |# postrun bashwrapper mods end --------------------------------- + | + |# last bashwrapper mods start ---------------------------------- + |${spaceCode(allMods.last)} + |# last bashwrapper mods end ------------------------------------ | |exit 0 |""".stripMargin @@ -347,22 +390,32 @@ object BashWrapper { } private def generateParsers(params: List[Argument[_]]) = { - // gather parse code for params + // no parsers should be generated for positional arguments, so remove these first val wrapperParams = params.filterNot(_.flags == "") + + // gather parse code for params val parseStrs = wrapperParams.map { - case bo: BooleanArgumentBase if bo.flagValue.isDefined => - val fv = bo.flagValue.get + case param: BooleanArgumentBase if param.flagValue.isDefined => + val flagValue = param.flagValue.get // params of the form --param - val part1 = argStore(bo.name, bo.VIASH_PAR, fv.toString, 1) + val part1 = generateBooleanFlagParser( + argName = param.name, + envName = param.VIASH_PAR, + value = flagValue + ) // Alternatives - val moreParts = bo.alternatives.map(alt => { - argStore(alt, bo.VIASH_PAR, fv.toString, 1) + val moreParts = param.alternatives.map(alt => { + generateBooleanFlagParser( + argName = alt, + envName = param.VIASH_PAR, + value = flagValue + ) }) (part1 :: moreParts).mkString("\n") case param => - val multisep = + val multiple_sep = if (param.multiple && param.direction == Input) { Some(param.multiple_sep) } else { @@ -370,18 +423,31 @@ object BashWrapper { } // params of the form --param ... - val part1 = param.flags match { - case "---" | "--" | "-" => argStore(param.name, param.VIASH_PAR, "\"$2\"", 2, multisep) - case "" => Nil - } + val part1 = generateFlagParser( + argName = param.name, + envName = param.VIASH_PAR, + multiple_sep = multiple_sep + ) + // params of the form --param=..., except -param=... is not allowed val part2 = param.flags match { - case "---" | "--" => List(argStoreSed(param.name, param.VIASH_PAR, multisep)) - case "-" | "" => Nil + case "---" | "--" => + List( + generateFlagWithEqualsParser( + argName = param.name, + envName = param.VIASH_PAR, + multiple_sep = multiple_sep + ) + ) + case "-" => Nil } // Alternatives - val moreParts = param.alternatives.map(alt => { - argStore(alt, param.VIASH_PAR, "\"$2\"", 2, multisep) + val moreParts = param.alternatives.map(alternativeFlag => { + generateFlagParser( + argName = alternativeFlag, + envName = param.VIASH_PAR, + multiple_sep = multiple_sep + ) }) (part1 :: part2 ::: moreParts).mkString("\n") @@ -395,17 +461,21 @@ object BashWrapper { } else { "\n# storing leftover values in positionals\n" + positionals.map { param => - if (param.multiple && param.direction == Input) { - s"""while [[ $$# -gt 0 ]]; do - | ${store("positionalArg", param.VIASH_PAR, "\"$1\"", Some(param.multiple_sep)).mkString("\n ")} - | shift 1 - |done""".stripMargin - } else { - s"""if [[ $$# -gt 0 ]]; then - | ${param.VIASH_PAR}="$$1" - | shift 1 - |fi""" - } + val storeStr = + s"""ViashParseArgumentValue "${param.name}" "${param.VIASH_PAR}" "${param.multiple}" "$$1"""" + + val (begin, end) = + if (param.multiple && param.direction == Input) { + ("while", "done") + } else { + ("if", "fi") + } + + s"""# processing positional values for '${param.name}' + |${begin} [[ $$# -gt 0 ]]; do + | ${storeStr} + | shift 1 + |${end}""".stripMargin }.mkString("\n") } @@ -720,8 +790,8 @@ object BashWrapper { val parsers = compArgs.flatMap{ case (flag, env, _) => List( - argStore(flag, env, "\"$2\"", 2), - argStoreSed(flag, env) + generateFlagParser(argName = flag, envName = env), + generateFlagWithEqualsParser(argName = flag, envName = env) ) }.map("\n" + _).mkString @@ -852,5 +922,39 @@ object BashWrapper { preRun = "\n# set dependency paths\n" + dependenciesStr ) } + + private def generateWorkDir(argsMetaAndDeps: Map[String, List[Argument[_]]]): BashWrapperMods = { + def renderStrs = argsMetaAndDeps.map{case (key, args) => + def renderYamlStrs = args.map(arg => { + s"""ViashRenderYamlKeyValue '${arg.name}' '${arg.`type`}' "${arg.multiple}" "$${${arg.VIASH_PAR}:-UNDEFINED}" >> "$$VIASH_WORK_PARAMS"""" + }) + s"""echo '${key}:' >> "$$VIASH_WORK_PARAMS" + |${renderYamlStrs.mkString("\n")}""".stripMargin + } + def preRun = + s"""VIASH_WORK_DIR=$$(mktemp -d "$$VIASH_META_TEMP_DIR/viash-run-testbash-XXXXXX") + |function clean_up { + | rm -rf "$$VIASH_WORK_DIR" + |} + |function interrupt { + | echo -e "\nCTRL-C Pressed..." + | exit 1 + |} + |trap clean_up EXIT + |trap interrupt INT SIGINT + | + |# Create params yaml + |VIASH_WORK_PARAMS="$$VIASH_WORK_DIR/params.yaml" + |touch "$$VIASH_WORK_PARAMS" + | + |${renderStrs.mkString("\n\n")} + | + |cat "$$VIASH_WORK_PARAMS" # cat it for now + |""".stripMargin + // todo: generate script here as well? + BashWrapperMods( + preRun = preRun + ) + } } From 921fa8b975ec3ee5739e80db6d4f2775d1399242 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Wed, 14 Aug 2024 09:47:01 +0200 Subject: [PATCH 16/18] fixes --- .../helpers/bashutils/ViashParseArgumentValue.sh | 6 +++--- src/main/scala/io/viash/helpers/Bash.scala | 1 + src/main/scala/io/viash/wrapper/BashWrapper.scala | 14 ++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/resources/io/viash/helpers/bashutils/ViashParseArgumentValue.sh b/src/main/resources/io/viash/helpers/bashutils/ViashParseArgumentValue.sh index ea66225bb..c54d8cfe1 100644 --- a/src/main/resources/io/viash/helpers/bashutils/ViashParseArgumentValue.sh +++ b/src/main/resources/io/viash/helpers/bashutils/ViashParseArgumentValue.sh @@ -1,7 +1,7 @@ function ViashParseArgumentValue { - local env_name="$1" - local multiple="$2" - local flag="$3" + local flag="$1" + local env_name="$2" + local multiple="$3" local value="$4" if [ $# -lt 4 ]; then diff --git a/src/main/scala/io/viash/helpers/Bash.scala b/src/main/scala/io/viash/helpers/Bash.scala index ad9753810..1fea9b62a 100644 --- a/src/main/scala/io/viash/helpers/Bash.scala +++ b/src/main/scala/io/viash/helpers/Bash.scala @@ -34,6 +34,7 @@ object Bash { lazy val ViashFindTargetDir: String = readUtils("ViashFindTargetDir") lazy val ViashDockerFuns: String = readUtils("ViashDockerFuns") lazy val ViashLogging: String = readUtils("ViashLogging") + lazy val ViashRenderYaml: String = readUtils("ViashRenderYaml") lazy val ViashParseArgumentValue: String = readUtils("ViashParseArgumentValue") /** diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index 1093c1ff6..f186e7bc1 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -287,13 +287,15 @@ object BashWrapper { | VIASH_TEMP=$${VIASH_TEMP:-/tmp} |fi | - |# define helper functions + |# bash helper functions start ----------------------------------- |${Bash.ViashQuote} |${Bash.ViashRemoveFlags} |${Bash.ViashSourceDir} |${Bash.ViashFindTargetDir} |${Bash.ViashLogging} |${Bash.ViashParseArgumentValue} + |${Bash.ViashRenderYaml} + |# bash helper functions end ------------------------------------- | |# find source folder of this component |VIASH_META_RESOURCES_DIR=`ViashSourceDir $${BASH_SOURCE[0]}` @@ -464,15 +466,15 @@ object BashWrapper { val storeStr = s"""ViashParseArgumentValue "${param.name}" "${param.VIASH_PAR}" "${param.multiple}" "$$1"""" - val (begin, end) = + val (begin, mid, end) = if (param.multiple && param.direction == Input) { - ("while", "done") + ("while", "do", "done") } else { - ("if", "fi") + ("if", "then", "fi") } s"""# processing positional values for '${param.name}' - |${begin} [[ $$# -gt 0 ]]; do + |${begin} [[ $$# -gt 0 ]]; ${mid} | ${storeStr} | shift 1 |${end}""".stripMargin @@ -926,7 +928,7 @@ object BashWrapper { private def generateWorkDir(argsMetaAndDeps: Map[String, List[Argument[_]]]): BashWrapperMods = { def renderStrs = argsMetaAndDeps.map{case (key, args) => def renderYamlStrs = args.map(arg => { - s"""ViashRenderYamlKeyValue '${arg.name}' '${arg.`type`}' "${arg.multiple}" "$${${arg.VIASH_PAR}:-UNDEFINED}" >> "$$VIASH_WORK_PARAMS"""" + s"""ViashRenderYamlKeyValue '${arg.plainName}' '${arg.`type`}' "${arg.multiple}" "$${${arg.VIASH_PAR}:-UNDEFINED}" >> "$$VIASH_WORK_PARAMS"""" }) s"""echo '${key}:' >> "$$VIASH_WORK_PARAMS" |${renderYamlStrs.mkString("\n")}""".stripMargin From 653b3284418dd68ee166002cb802034ca4328319 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Wed, 14 Aug 2024 09:50:25 +0200 Subject: [PATCH 17/18] more fixes --- src/main/scala/io/viash/wrapper/BashWrapper.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index f186e7bc1..150e297f4 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -926,15 +926,21 @@ object BashWrapper { } private def generateWorkDir(argsMetaAndDeps: Map[String, List[Argument[_]]]): BashWrapperMods = { - def renderStrs = argsMetaAndDeps.map{case (key, args) => - def renderYamlStrs = args.map(arg => { - s"""ViashRenderYamlKeyValue '${arg.plainName}' '${arg.`type`}' "${arg.multiple}" "$${${arg.VIASH_PAR}:-UNDEFINED}" >> "$$VIASH_WORK_PARAMS"""" + val renderStrs = argsMetaAndDeps.map{case (key, args) => + val renderYamlStrs = args.map(arg => { + val value = + if (arg.multiple) { + s"$${${arg.VIASH_PAR}[@]}" + } else { + s"$${${arg.VIASH_PAR}:-UNDEFINED}" + } + s"""ViashRenderYamlKeyValue '${arg.plainName}' '${arg.`type`}' "${arg.multiple}" "${value}" >> "$$VIASH_WORK_PARAMS"""" }) s"""echo '${key}:' >> "$$VIASH_WORK_PARAMS" |${renderYamlStrs.mkString("\n")}""".stripMargin } - def preRun = + val preRun = s"""VIASH_WORK_DIR=$$(mktemp -d "$$VIASH_META_TEMP_DIR/viash-run-testbash-XXXXXX") |function clean_up { | rm -rf "$$VIASH_WORK_DIR" From 18f594b4a3612acf083edde2178b0b1cdd2ba9bc Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Wed, 14 Aug 2024 09:57:30 +0200 Subject: [PATCH 18/18] only input multiples are arrays --- src/main/scala/io/viash/wrapper/BashWrapper.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/io/viash/wrapper/BashWrapper.scala b/src/main/scala/io/viash/wrapper/BashWrapper.scala index 150e297f4..ee8b4b42c 100644 --- a/src/main/scala/io/viash/wrapper/BashWrapper.scala +++ b/src/main/scala/io/viash/wrapper/BashWrapper.scala @@ -928,13 +928,14 @@ object BashWrapper { private def generateWorkDir(argsMetaAndDeps: Map[String, List[Argument[_]]]): BashWrapperMods = { val renderStrs = argsMetaAndDeps.map{case (key, args) => val renderYamlStrs = args.map(arg => { + val multiple = arg.multiple && arg.direction == Input val value = - if (arg.multiple) { + if (multiple) { s"$${${arg.VIASH_PAR}[@]}" } else { s"$${${arg.VIASH_PAR}:-UNDEFINED}" } - s"""ViashRenderYamlKeyValue '${arg.plainName}' '${arg.`type`}' "${arg.multiple}" "${value}" >> "$$VIASH_WORK_PARAMS"""" + s"""ViashRenderYamlKeyValue '${arg.plainName}' '${arg.`type`}' "${multiple}" "${value}" >> "$$VIASH_WORK_PARAMS"""" }) s"""echo '${key}:' >> "$$VIASH_WORK_PARAMS" |${renderYamlStrs.mkString("\n")}""".stripMargin