developer tip

다른 그루비에 그루비 스크립트 포함

optionbox 2020. 9. 11. 07:54
반응형

다른 그루비에 그루비 스크립트 포함


다른 groovy 스크립트에서 groovy 파일을 가져 오는 방법을 읽었습니다.

하나의 그루비 파일에 공통 함수를 정의하고 다른 그루비 파일에서 해당 함수를 호출하고 싶습니다.

나는 이것이 스크립팅 언어처럼 Groovy를 사용한다는 것을 이해합니다. 즉, 클래스 / 객체가 필요하지 않습니다. 나는 그루비에서 할 수있는 dsl과 같은 것을 시도하고 있습니다. 모든 변수는 Java에서 어설 션되며 쉘에서 그루비 스크립트를 실행하고 싶습니다.

이것이 가능합니까? 누군가가 몇 가지 예를 제공 할 수 있습니까?


evaluate(new File("../tools/Tools.groovy"))

그것을 스크립트 맨 위에 놓으십시오. 그러면 그루비 파일의 내용이 표시됩니다 (큰 따옴표 사이의 파일 이름을 그루비 스크립트로 바꾸면됩니다).

놀랍게도 "Tools.groovy"라는 클래스로이 작업을 수행합니다.


Groovy 2.2부터는 새로운 @BaseScriptAST 변환 주석을 사용하여 기본 스크립트 클래스를 선언 할 수 있습니다 .

예:

MainScript.groovy 파일 :

abstract class MainScript extends Script {
    def meaningOfLife = 42
}

test.groovy 파일 :

import groovy.transform.BaseScript
@BaseScript MainScript mainScript

println "$meaningOfLife" //works as expected

이를 수행하는 또 다른 방법은 그루비 클래스에서 함수를 정의하고 런타임에 파일을 구문 분석하고 클래스 경로에 추가하는 것입니다.

File sourceFile = new File("path_to_file.groovy");
Class groovyClass = new GroovyClassLoader(getClass().getClassLoader()).parseClass(sourceFile);
GroovyObject myObject = (GroovyObject) groovyClass.newInstance();

최선의 선택은 그루비 클래스의 형태로 유틸리티를 구성하고, 클래스 경로에 추가하고, 메인 스크립트가 import 키워드를 통해 참조하도록하는 것입니다.

예:

scripts / DbUtils.groovy

class DbUtils{
    def save(something){...}
}

scripts / script1.groovy :

import DbUtils
def dbUtils = new DbUtils()
def something = 'foobar'
dbUtils.save(something)

실행 스크립트 :

cd scripts
groovy -cp . script1.groovy

이 작업을 수행하는 방법은 GroovyShell.

GroovyShell shell = new GroovyShell()
def Util = shell.parse(new File('Util.groovy'))
def data = Util.fetchData()

Groovy doesn't have an import keyword like typical scripting languages that will do a literal include of another file's contents (alluded to here: Does groovy provide an include mechanism?).
Because of its object/class oriented nature, you have to "play games" to make things like this work. One possibility is to make all your utility functions static (since you said they don't use objects) and then perform a static import in the context of your executing shell. Then you can call these methods like "global functions".
Another possibility would be using a Binding object (http://groovy.codehaus.org/api/groovy/lang/Binding.html) while creating your Shell and binding all the functions you want to the methods (the downside here would be having to enumerate all methods in the binding but you could perhaps use reflection). Yet another solution would be to override methodMissing(...) in the delegate object assigned to your shell which allows you to basically do dynamic dispatch using a map or whatever method you'd like.

Several of these methods are demonstrated here: http://www.nextinstruction.com/blog/2012/01/08/creating-dsls-with-groovy/. Let me know if you want to see an example of a particular technique.


Here's a complete example of including one script within another.
Just run the Testmain.groovy file
Explanatory comments included because I'm nice like that ;]

Testutils.groovy

// This is the 'include file'
// Testmain.groovy will load it as an implicit class
// Each method in here will become a method on the implicit class

def myUtilityMethod(String msg) {
    println "myUtilityMethod running with: ${msg}"
}

Testmain.groovy

// Run this file

// evaluate implicitly creates a class based on the filename specified
evaluate(new File("./Testutils.groovy"))
// Safer to use 'def' here as Groovy seems fussy about whether the filename (and therefore implicit class name) has a capital first letter
def tu = new Testutils()
tu.myUtilityMethod("hello world")

How about treat the external script as a Java class? Based on this article: https://www.jmdawson.net/blog/2014/08/18/using-functions-from-one-groovy-script-in-another/

getThing.groovy The external script

def getThingList() {
    return ["thing","thin2","thing3"]
}

printThing.groovy The main script

thing = new getThing()  // new the class which represents the external script
println thing.getThingList()

Result

$ groovy printThing.groovy
[thing, thin2, thing3]

For late-comers, it appears that groovy now support the :load file-path command which simply redirects input from the given file, so it is now trivial to include library scripts.

It works as input to the groovysh & as a line in a loaded file:
groovy:000> :load file1.groovy

file1.groovy can contain:
:load path/to/another/file invoke_fn_from_file();


A combination of @grahamparks and @snowindy answers with a couple of modifications is what worked for my Groovy scripts running on Tomcat:

Utils.groovy

class Utils {
    def doSth() {...}
}

MyScript.groovy:

/* import Utils --> This import does not work. The class is not even defined at this time */
Class groovyClass = new GroovyClassLoader(getClass().getClassLoader()).parseClass(new File("full_path_to/Utils.groovy")); // Otherwise it assumes current dir is $CATALINA_HOME
def foo = groovyClass.newInstance(); // 'def' solves compile time errors!!
foo.doSth(); // Actually works!

Groovy can import other groovy classes exactly like Java does. Just be sure the extension of the library file is .groovy.

    $ cat lib/Lib.groovy
    package lib
    class Lib {
       static saySomething() { println 'something' }
       def sum(a,b) { a+b }
    }

    $ cat app.gvy
    import lib.Lib
    Lib.saySomething();
    println new Lib().sum(37,5)

    $ groovy app
    something
    42

After some investigation I have come to the conclusion that the following approach seems the best.

some/subpackage/Util.groovy

@GrabResolver(name = 'nexus', root = 'https://local-nexus-server:8443/repository/maven-public', m2Compatible = true)
@Grab('com.google.errorprone:error_prone_annotations:2.1.3')
@Grab('com.google.guava:guava:23.0')
@GrabExclude('com.google.errorprone:error_prone_annotations')

import com.google.common.base.Strings

class Util {
    void msg(int a, String b, Map c) {
        println 'Message printed by msg method inside Util.groovy'
        println "Print 5 asterisks using the Guava dependency ${Strings.repeat("*", 5)}"
        println "Arguments are a=$a, b=$b, c=$c"
    }
}

example.groovy

#!/usr/bin/env groovy
Class clazz = new GroovyClassLoader().parseClass("${new File(getClass().protectionDomain.codeSource.location.path).parent}/some/subpackage/Util.groovy" as File)
GroovyObject u = clazz.newInstance()
u.msg(1, 'b', [a: 'b', c: 'd'])

In order to run the example.groovy script, add it to your system path and type from any directory:

example.groovy

The script prints:

Message printed by msg method inside Util.groovy
Print 5 asterisks using the Guava dependency *****
Arguments are a=1, b=b, c=[a:b, c:d]

The above example was tested in the following environment: Groovy Version: 2.4.13 JVM: 1.8.0_151 Vendor: Oracle Corporation OS: Linux

The example demonstrates the following:

  • How to use a Util class inside a groovy script.
  • A Util class calling the Guava third party library by including it as a Grape dependency (@Grab('com.google.guava:guava:23.0')).
  • The Util class can reside in a subdirectory.
  • Passing arguments to a method within the Util class.

Additional comments/suggestions:

  • Always use a groovy class instead of groovy script for reusable functionality within your groovy scripts. The above example uses the Util class defined in the Util.groovy file. Using groovy scripts for reusable functionality is problematic. For example, if using a groovy script then the Util class would have to be instantiated at the bottom of the script with new Util(), but most importantly it would have to be placed in a file named anything but Util.groovy. Refer to Scripts versus classes for more details about the differences between groovy scripts and groovy classes.
  • In the above example I use the path "${new File(getClass().protectionDomain.codeSource.location.path).parent}/some/subpackage/Util.groovy" instead of "some/subpackage/Util.groovy". This will guarantee that the Util.groovy file will always be found in relation to the groovy script's location (example.groovy) and not the current working directory. For example, using "some/subpackage/Util.groovy" would result in searching at WORK_DIR/some/subpackage/Util.groovy.
  • Follow the Java class naming convention to name your groovy scripts. I personally prefer a small deviation where the scripts start with a lower letter instead of a capital one. For example, myScript.groovy is a script name, and MyClass.groovy is a class name. Naming my-script.groovy will result in runtime errors in certain scenarios because the resulting class will not have a valid Java class name.
  • In the JVM world in general the relevant functionality is named JSR 223: Scripting for the Java. In groovy in particular the functionality is named Groovy integration mechanisms. In fact, the same approach can be used in order to call any JVM language from within Groovy or Java. Some notable examples of such JVM languages are Groovy, Java, Scala, JRuby, and JavaScript (Rhino).

참고URL : https://stackoverflow.com/questions/9136328/including-a-groovy-script-in-another-groovy

반응형