Skip to content

Commit 9a56fce

Browse files
committed
add argument to bash script
1 parent 51f6fa2 commit 9a56fce

2 files changed

Lines changed: 98 additions & 13 deletions

File tree

src/main/scala/io/viash/config/resources/BashScript.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ case class BashScript(
3333
is_executable: Option[Boolean] = Some(true),
3434
parent: Option[URI] = None,
3535

36+
@description("""Whether to use jq for JSON parameter parsing and store multiple-value arguments as Bash arrays.
37+
| - `true`: Use jq for JSON parsing. Arguments with `multiple: true` are stored as Bash arrays (e.g. `par_input=("a" "b" "c")`). Requires jq to be installed.
38+
| - `false`: Use the built-in JSON parser. Arguments with `multiple: true` are stored as separator-delimited strings (e.g. `par_input="a;b;c"`), using the argument's `multiple_sep` (default `";"`).
39+
| - Not specified (default): Same behavior as `false`, but a deprecation warning is shown at build time indicating that the default will change to `true` in a future version of Viash.""")
40+
@example("use_jq: true", "yaml")
41+
@since("Viash 0.10.0")
42+
use_jq: Option[Boolean] = None,
43+
3644
@description("Specifies the resource as a Bash script.")
3745
`type`: String = "bash_script"
3846
) extends Script {

src/main/scala/io/viash/languages/Bash.scala

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717

1818
package io.viash.languages
1919

20-
import io.viash.helpers.Resources
20+
import io.viash.helpers.{Resources, Logger}
2121
import io.viash.config.arguments._
2222
import io.viash.config.Config
23-
import io.viash.config.resources.ScriptInjectionMods
23+
import io.viash.config.resources.{ScriptInjectionMods, BashScript}
2424

2525
object Bash extends Language {
2626
val id: String = "bash"
@@ -29,29 +29,102 @@ object Bash extends Language {
2929
val commentStr: String = "#"
3030
val executor: Seq[String] = Seq("bash")
3131
val viashParseJsonCode: String = Resources.read("languages/bash/ViashParseJson.sh")
32+
val viashParseJsonCompatCode: String = Resources.read("languages/bash/ViashParseJsonCompatibility.sh")
33+
34+
private val logger = Logger("Bash")
3235

3336
def generateInjectionMods(argsMetaAndDeps: Map[String, List[Argument[_]]], config: Config): ScriptInjectionMods = {
34-
val fullCode = s"""${viashParseJsonCode}
37+
// Determine use_jq setting from the BashScript resource
38+
val useJq = config.resources.collectFirst {
39+
case bs: BashScript => bs.use_jq
40+
}.flatten
41+
42+
useJq match {
43+
case Some(true) =>
44+
generateJqInjectionMods(argsMetaAndDeps)
45+
case Some(false) =>
46+
generateCompatInjectionMods(argsMetaAndDeps)
47+
case None =>
48+
logger.warn(
49+
"Deprecation warning: 'use_jq' is not set for bash_script resource. " +
50+
"Currently defaulting to compatibility mode (built-in parser, separator-delimited strings for multiple-value arguments). " +
51+
"In a future version of Viash, the default will change to 'use_jq: true', " +
52+
"which requires jq to be installed. " +
53+
"Please set 'use_jq: true' or 'use_jq: false' explicitly in your bash_script resource to silence this warning."
54+
)
55+
generateCompatInjectionMods(argsMetaAndDeps)
56+
}
57+
}
58+
59+
/**
60+
* Generate injection mods for compatibility mode:
61+
* Uses the built-in bash JSON parser, then converts multiple-value
62+
* arguments from bash arrays to separator-delimited strings.
63+
*/
64+
private def generateCompatInjectionMods(argsMetaAndDeps: Map[String, List[Argument[_]]]): ScriptInjectionMods = {
65+
val parseCode = s"""${viashParseJsonCompatCode}
3566

3667
# Parse JSON parameters
3768
_viash_json_content=$$(cat "$$VIASH_WORK_PARAMS")
3869
ViashParseJsonBash <<< "$$_viash_json_content"
39-
4070
"""
71+
72+
// Convert multiple-value arguments from arrays to IFS-separated strings
73+
val multipleArgs = argsMetaAndDeps.toList.flatMap { case (_, args) =>
74+
args.collect {
75+
case arg if arg.multiple =>
76+
val sep = arg.multiple_sep
77+
s"""${arg.par}="$$(IFS='${sep}'; printf '%s' "$${${arg.par}[*]}")""""
78+
}
79+
}
80+
81+
val fullCode = if (multipleArgs.nonEmpty) {
82+
parseCode + "\n# Convert arrays to separator-delimited strings for compatibility\n" +
83+
multipleArgs.mkString("\n") + "\n"
84+
} else {
85+
parseCode
86+
}
87+
4188
ScriptInjectionMods(params = fullCode)
4289
}
4390

91+
/**
92+
* Generate injection mods for jq mode:
93+
* Uses jq to parse JSON and populate bash variables directly.
94+
* Multiple-value arguments are stored as bash arrays.
95+
*/
96+
private def generateJqInjectionMods(argsMetaAndDeps: Map[String, List[Argument[_]]]): ScriptInjectionMods = {
97+
val parseCode = s"""${viashParseJsonCode}
98+
99+
# Parse JSON parameters using jq
100+
_viash_json_content=$$(cat "$$VIASH_WORK_PARAMS")
101+
ViashParseJsonBash <<< "$$_viash_json_content"
102+
"""
103+
ScriptInjectionMods(params = parseCode)
104+
}
105+
44106
def generateConfigInjectMods(argsMetaAndDeps: Map[String, List[Argument[_]]], config: Config): ScriptInjectionMods = {
107+
// Determine use_jq setting from the BashScript resource
108+
val useJq = config.resources.collectFirst {
109+
case bs: BashScript => bs.use_jq
110+
}.flatten
111+
val useArrays = useJq.contains(true)
112+
45113
val parSet = argsMetaAndDeps.flatMap { case (_, params) =>
46114
params.map { par =>
47-
val value = getExampleValue(par)
115+
val value = getExampleValue(par, useArrays)
48116
if (par.multiple) {
49-
// For multiple values, create a bash array
50-
val values = value match {
51-
case v if v.isEmpty => ""
52-
case v => v
117+
if (useArrays) {
118+
// jq mode: bash array
119+
val values = value match {
120+
case v if v.isEmpty => ""
121+
case v => v
122+
}
123+
s"""${par.par}=($values)"""
124+
} else {
125+
// compat mode: IFS-separated string
126+
s"""${par.par}='$value'"""
53127
}
54-
s"""${par.par}=($values)"""
55128
} else {
56129
s"""${par.par}='$value'"""
57130
}
@@ -62,11 +135,15 @@ ViashParseJsonBash <<< "$$_viash_json_content"
62135
ScriptInjectionMods(params = paramsCode)
63136
}
64137

65-
private def getExampleValue(arg: Argument[_]): String = {
138+
private def getExampleValue(arg: Argument[_], useArrays: Boolean = true): String = {
66139
val values = getArgumentValues(arg)
67-
140+
68141
if (arg.multiple) {
69-
values.map(v => s"'$v'").mkString(" ")
142+
if (useArrays) {
143+
values.map(v => s"'$v'").mkString(" ")
144+
} else {
145+
values.mkString(arg.multiple_sep)
146+
}
70147
} else {
71148
values.headOption.getOrElse("")
72149
}

0 commit comments

Comments
 (0)