Groovy多行字符串插值空格

4

我正在尝试生成一些通用的Jenkins Groovy代码,但似乎在多行字符串和额外空格上遇到了问题。我已经尝试了通过谷歌搜索找到的所有方法,但好像不能让它正常工作。

我的问题不是简单的多行字符串相关。对于简单情况,我使用stripIndent()和stripMargin()方法来裁剪空白字符。我的问题是由于字符串中有插值方法而引起的。

Groovy信息:Groovy版本:3.0.2 JVM:13.0.2 厂商:Oracle Corporation 操作系统:Mac OS X

String method2(String tier, String jobName) {
    return """
            Map downstreamJobs = [:]
            stage ("${jobName}-${tier}-\${region}_${jobName}") {
                test
            }
        """.stripIndent().stripMargin()
}

static String simpleLog() {
    return """
            script {
               def user = env.BUILD_USER_ID
            }
          """.stripIndent().stripMargin()
}

static String method1() {
    return """\
            import jenkins.model.Jenkins
            currentBuild.displayName = "name"

            ${simpleLog()}
        """.stripIndent().stripMargin()
}

String generateFullDeploymentPipelineCode() {
    return """Text here
            ${method1()}
            ${method2("test1", "test2")}
            """.stripIndent().stripMargin()
}

println(generateFullDeploymentPipelineCode())

这是它所打印的内容(或写入磁盘):
Text here
                      import jenkins.model.Jenkins
          currentBuild.displayName = "name"

script {
   def user = env.BUILD_USER_ID
}



Map downstreamJobs = [:]
stage ("test2-test1-${region}_test2") {
    test
}

为什么导入行周围会有额外的空格?我知道缩进方法应该根据最少数量的前导空格修剪所有空格,所以这就是我们使用反斜杠的原因(例如在此处https://dev59.com/lmIj5IYBdhLWcg3wy3_H#19882917)。这对于简单字符串有效,但一旦你开始使用插值,它就会出现问题。并不是常规变量,而是当你插入整个方法时。

当您通过插值插入字符串时,只需缩进其第一行。插入的字符串的后续行将以不同的缩进方式进行缩进,这会搞乱一切。 - zett42
好的,明白了,那么有没有什么方法可以规避这个问题?还是整个过程都毫无意义? - Serban Cezar
将所有内容移动到单独的模板中?SimpleTemplateEngine - daggett
2个回答

2
作为一种选择,只需在最终字符串上使用stripMargin()一次即可。
String method2(String tier, String jobName) {
    return """\
            |Map downstreamJobs = [:]
            |stage ("${jobName}-${tier}-\${region}_${jobName}") {
            |    test
            |}
        """
}

static String simpleLog() {
    return """\
            |script {
            |   def user = env.BUILD_USER_ID
            |}
          """
}

static String method1() {
    return """\
            |import jenkins.model.Jenkins
            |currentBuild.displayName = "name"

            ${simpleLog()}
        """
}

String generateFullDeploymentPipelineCode() {
    return """\
            |Text here
            ${method1()}
            ${method2("test1", "test2")}
            """.stripIndent().stripMargin()
}

println(generateFullDeploymentPipelineCode())

结果:

Text here
import jenkins.model.Jenkins
currentBuild.displayName = "name"

script {
   def user = env.BUILD_USER_ID
}

Map downstreamJobs = [:]
stage ("test2-test1-${region}_test2") {
    test
}

使用trim()和stripIndent()的另一种变体

def method2(String tier, String jobName) {
    return """
            Map downstreamJobs = [:]
            stage ("${jobName}-${tier}-\${region}_${jobName}") {
                test
            }
        """.trim()
}

def simpleLog() {
    return """
            script {
               def user = env.BUILD_USER_ID
            }
          """.trim()
}

def method1() {
    return """
            import jenkins.model.Jenkins
            currentBuild.displayName = "name"
            ${simpleLog()}
        """.trim()
}

def generateFullDeploymentPipelineCode() {
    return """\
            Text here
            ${method1()}
            ${method2("test1", "test2")}
            """.stripIndent()
}

println(generateFullDeploymentPipelineCode())

是的,我知道我可以使用占位符,比如管道符号,但那样代码看起来很糟糕。我不想为每一行代码都添加一个管道字符,或者几乎每一行都添加,这会变得混乱。虽然这仍然是一种解决方案,我承认。 - Serban Cezar
添加了另一种变体,使用trim+stripIndent。 - daggett

1
当您使用插值法插入字符串时,只需缩进其第一行。插入字符串的后续行将以不同的缩进方式缩进,这会使一切都变得混乱。
使用 GString 的一些不太知名的成员(即.strings[].values[]),我们可以对每个插入值的所有行进行缩进对齐。
String method2(String tier, String jobName) {
    indented """
        Map downstreamJobs = [:]
        stage ("${jobName}-${tier}-\${region}_${jobName}") {
            test
        }
    """
}

String simpleLog() {
    indented """\
        script {
           def user = env.BUILD_USER_ID
        }
    """
}

String method1() {
    indented """\
        import jenkins.model.Jenkins
        currentBuild.displayName = "name"

        ${simpleLog()}
    """
}

String generateFullDeploymentPipelineCode() {
    indented """\
        Text here
        ${method1()}
        ${method2("test1", "test2")}
    """
}

println generateFullDeploymentPipelineCode()

//---------- Move the following code into its own script ----------

// Function to adjust the indentation of interpolated values so that all lines
// of a value match the indentation of the first line.
// Finally stripIndent() will be called before returning the string.

String indented( GString templ ) {

    // Iterate over the interpolated values of the GString template.
    templ.values.eachWithIndex{ value, i ->

        // Get the string preceding the current value. Always defined, even
        // when the value is at the beginning of the template.
        def beforeValue = templ.strings[ i ]

        // RegEx to match any indent substring before the value.
        // Special case for the first string, which doesn't necessarily contain '\n'. 
        def regexIndent = i == 0
                          ? /(?:^|\n)([ \t]+)$/
                          : /\n([ \t]+)$/

        def matchIndent = ( beforeValue =~ regexIndent )
        if( matchIndent ) {
            def indent = matchIndent[ 0 ][ 1 ]
            def lines = value.readLines()
            def linesNew = [ lines.head() ]  // The 1st line is already indented.
            // Insert the indentation from the 1st line into all subsequent lines.
            linesNew += lines.tail().collect{ indent + it }
            // Finally replace the value with the reformatted lines.
            templ.values[ i ] = linesNew.join('\n')
        }
    }

    return templ.stripIndent()
}

// Fallback in case the input string is not a GString (when it doesn't contain expressions)
String indented( String templ ) {
    return templ.stripIndent()  
}

在Codingground上的实时演示

输出:

Text here
import jenkins.model.Jenkins
currentBuild.displayName = "name"

script {
   def user = env.BUILD_USER_ID
}

Map downstreamJobs = [:]
stage ("test2-test1-${region}_test2") {
    test
}

结论:

通过使用indented函数,实现了从GString模板生成代码的简洁Groovy语法。

这是一个非常有收获的学习经历。我最初尝试使用evaluate函数进行完全不同的尝试,但结果过于复杂且不够灵活。然后我随机浏览了一些mrhaki blog的帖子(总是非常有趣!),直到我发现了"Groovy Goodness:更多了解GString"。这是实现此解决方案的关键。


这非常有趣,非常感谢。GString文档值得一读。 - Serban Cezar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接