diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java index 82da554bf4..6bef0f076a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java @@ -17,6 +17,7 @@ package ghidra.app.script; import java.io.IOException; import java.io.PrintWriter; +import java.util.regex.Pattern; import generic.jar.ResourceFile; import ghidra.util.classfinder.ExtensionPoint; @@ -93,6 +94,24 @@ public abstract class GhidraScriptProvider public abstract void createNewScript(ResourceFile newScript, String category) throws IOException; + /** + * Returns a Pattern that matches block comment openings. + * If block comments are not supported by this provider, then this returns null. + * @return the Pattern for block comment openings, null if block comments are not supported + */ + public Pattern getBlockCommentStart() { + return null; + } + + /** + * Returns a Pattern that matches block comment closings. + * If block comments are not supported by this provider, then this returns null. + * @return the Pattern for block comment closings, null if block comments are not supported + */ + public Pattern getBlockCommentEnd() { + return null; + } + /** * Returns the comment character. * For example, "//" or "#". diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java index 8f3539576b..1a433dd2cc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java @@ -17,6 +17,7 @@ package ghidra.app.script; import java.io.*; import java.util.Collections; +import java.util.regex.Pattern; import org.osgi.framework.Bundle; @@ -26,6 +27,9 @@ import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; public class JavaScriptProvider extends GhidraScriptProvider { + private static final Pattern BLOCK_COMMENT_START = Pattern.compile("/\\*"); + private static final Pattern BLOCK_COMMENT_END = Pattern.compile("\\*/"); + private final BundleHost bundleHost; /** @@ -159,6 +163,26 @@ public class JavaScriptProvider extends GhidraScriptProvider { writer.close(); } + /** + * Returns a Pattern that matches block comment openings. + * For Java this is "/*". + * @return the Pattern for Java block comment openings + */ + @Override + public Pattern getBlockCommentStart() { + return BLOCK_COMMENT_START; + } + + /** + * Returns a Pattern that matches block comment closings. + * In Java this is an asterisk followed by a forward slash. + * @return the Pattern for Java block comment closings + */ + @Override + public Pattern getBlockCommentEnd() { + return BLOCK_COMMENT_END; + } + @Override public String getCommentCharacter() { return "//"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java index f92c30647f..49bca25d09 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java @@ -20,6 +20,8 @@ import static ghidra.util.HTMLUtilities.*; import java.io.*; import java.util.List; import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.swing.ImageIcon; import javax.swing.KeyStroke; @@ -187,60 +189,39 @@ public class ScriptInfo { // Note that skipping certification header presumes that the header // is intact with an appropriate start and end String certifyHeaderStart = provider.getCertifyHeaderStart(); - String certifyHeaderEnd = provider.getCertifyHeaderEnd(); - String certifyHeaderBodyPrefix = provider.getCertificationBodyPrefix(); boolean allowCertifyHeader = (certifyHeaderStart != null); - boolean skipCertifyHeader = false; - BufferedReader reader = null; - try { - StringBuffer buffer = new StringBuffer(); + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(sourceFile.getInputStream()))) { + StringBuilder buffer = new StringBuilder(); boolean hitAtSign = false; - reader = new BufferedReader(new InputStreamReader(sourceFile.getInputStream())); while (true) { String line = reader.readLine(); if (line == null) { break; } - if (allowCertifyHeader) { - // Skip past certification header if found - if (skipCertifyHeader) { - String trimLine = line.trim(); - if (trimLine.startsWith(certifyHeaderEnd)) { - allowCertifyHeader = false; - skipCertifyHeader = false; - continue; - } - if (certifyHeaderBodyPrefix == null || - trimLine.startsWith(certifyHeaderBodyPrefix)) { - continue; // skip certification header body - } - // broken certification header - unexpected line - Msg.error(this, - "Script contains invalid certification header: " + getName()); - allowCertifyHeader = false; - skipCertifyHeader = false; - } - else if (line.startsWith(certifyHeaderStart)) { - skipCertifyHeader = true; - continue; - } + if (allowCertifyHeader && skipCertifyHeader(reader, line)) { + allowCertifyHeader = false; + continue; + } + + if (parseBlockComment(reader, line)) { + allowCertifyHeader = false; + continue; // read block comment; move to next line } if (line.startsWith(commentPrefix)) { allowCertifyHeader = false; line = line.substring(commentPrefix.length()).trim(); - if (line.startsWith("@")) { hitAtSign = true; parseMetaDataLine(line); } else if (!hitAtSign) { - buffer.append(line); - buffer.append(' '); - buffer.append('\n'); + // only consume line comments that come before metadata + buffer.append(line).append(' ').append('\n'); } } else if (line.trim().isEmpty()) { @@ -257,16 +238,62 @@ public class ScriptInfo { catch (IOException e) { Msg.debug(this, "Unexpected exception reading script: " + sourceFile, e); } - finally { - if (reader != null) { - try { - reader.close(); - } - catch (IOException e) { - // don't care; we tried - } - } + } + + private boolean skipCertifyHeader(BufferedReader reader, String line) throws IOException { + + // Note that skipping certification header presumes that the header + // is intact with an appropriate start and end + String certifyHeaderStart = provider.getCertifyHeaderStart(); + if (certifyHeaderStart == null) { + return false; } + + if (!line.startsWith(certifyHeaderStart)) { + return false; + } + + String certifyHeaderEnd = provider.getCertifyHeaderEnd(); + String certifyHeaderBodyPrefix = provider.getCertificationBodyPrefix(); + certifyHeaderBodyPrefix = certifyHeaderBodyPrefix == null ? "" : certifyHeaderBodyPrefix; + + while ((line = reader.readLine()) != null) { + + // Skip past certification header if found + String trimLine = line.trim(); + if (trimLine.startsWith(certifyHeaderEnd)) { + return true; + } + + if (trimLine.startsWith(certifyHeaderBodyPrefix)) { + continue; // skip certification header body + } + + // broken certification header - unexpected line + Msg.error(this, + "Script contains invalid certification header: " + getName()); + } + return false; + } + + private boolean parseBlockComment(BufferedReader reader, String line) throws IOException { + Pattern blockStart = provider.getBlockCommentStart(); + Pattern blockEnd = provider.getBlockCommentEnd(); + + if (blockStart == null || blockEnd == null) { + return false; + } + + Matcher startMatcher = blockStart.matcher(line); + if (startMatcher.find()) { + int lastOffset = startMatcher.end(); + while (line != null && !blockEnd.matcher(line).find(lastOffset)) { + line = reader.readLine(); + lastOffset = 0; + } + return true; + } + return false; } private void parseMetaDataLine(String line) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index f787212819..6f5dd34f81 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -528,6 +528,18 @@ public abstract class AbstractGhidraScriptMgrPluginTest return new ResourceFile(tempFile); } + protected ResourceFile createTempScriptFileWithLines(String... lines) throws IOException { + ResourceFile newScript = createTempScriptFile(); + + PrintWriter writer = new PrintWriter(newScript.getOutputStream()); + for (String line : lines) { + writer.println(line); + } + writer.close(); + + return newScript; + } + protected String changeEditorContents() { assertNotNull("Editor not opened and initialized", editorTextArea); assertNotNull("Editor not opened and initialized", buffer); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/JavaScriptInfoTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/JavaScriptInfoTest.java new file mode 100644 index 0000000000..012dda1cf3 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/JavaScriptInfoTest.java @@ -0,0 +1,214 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.script; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import javax.swing.KeyStroke; + +import org.junit.Test; + +import generic.jar.ResourceFile; +import ghidra.app.script.GhidraScriptUtil; +import ghidra.app.script.ScriptInfo; + +public class JavaScriptInfoTest extends AbstractGhidraScriptMgrPluginTest { + + @Test + public void testDetailedJavaScript() { + String descLine1 = "This script exists to check that the info on"; + String descLine2 = "a script that has extensive documentation is"; + String descLine3 = "properly parsed and represented."; + String author = "Fake Name"; + String categoryTop = "Test"; + String categoryBottom = "ScriptInfo"; + String keybinding = "ctrl shift COMMA"; + String menupath = "File.Run.Detailed Script"; + String importPackage = "detailStuff"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempScriptFileWithLines( + "/*", + " * This is a test block comment. It will be ignored.", + " * @category NotTheRealCategory", + " */", + "//" + descLine1, + "//" + descLine2, + "//" + descLine3, + "//@author " + author, + "//@category " + categoryTop + "." + categoryBottom, + "//@keybinding " + keybinding, + "//@menupath " + menupath, + "//@importpackage " + importPackage, + "class DetailedScript {", + " // for a blank class, it sure is well documented!", + "}"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + + String expectedDescription = descLine1 + " \n" + descLine2 + " \n" + descLine3 + " \n"; + assertEquals(expectedDescription, info.getDescription()); + + assertEquals(author, info.getAuthor()); + assertEquals(KeyStroke.getKeyStroke(keybinding), info.getKeyBinding()); + assertEquals(menupath.replace(".", "->"), info.getMenuPathAsString()); + assertEquals(importPackage, info.getImportPackage()); + + String[] actualCategory = info.getCategory(); + assertEquals(2, actualCategory.length); + assertEquals(categoryTop, actualCategory[0]); + assertEquals(categoryBottom, actualCategory[1]); + } + + @Test + public void testJavaScriptWithBlockComment() { + String description = "Script with a block comment at the top."; + String category = "Test"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempScriptFileWithLines( + "/*", + " * This is a test block comment. It will be ignored.", + " * @category NotTheRealCategory", + " */", + "//" + description, + "//@category " + category, + "class BlockCommentScript {", + " // just a blank class, nothing to see here", + "}"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + assertEquals(description + " \n", info.getDescription()); + + String[] actualCategory = info.getCategory(); + assertEquals(1, actualCategory.length); + assertEquals(category, actualCategory[0]); + } + + @Test + public void testJavaScriptWithBlockCommentAndCertifyHeader() { + String description = "Script with a block comment at the top."; + String category = "Test"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempScriptFileWithLines( + "/* ###" + + " * IP: GHIDRA" + + " * " + + " * Some license text..." + + " * you may not use this file except in compliance with the License." + + " * " + + " * blah blah blah" + + " */" + + " " + + "/*", + " * This is a test block comment. It will be ignored.", + " * @category NotTheRealCategory", + " */", + "//" + description, + "//@category " + category, + "class BlockCommentScript {", + " // just a blank class, nothing to see here", + "}"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + assertEquals(description + " \n", info.getDescription()); + + String[] actualCategory = info.getCategory(); + assertEquals(1, actualCategory.length); + assertEquals(category, actualCategory[0]); + } + + @Test + public void testJavaScriptWithoutBlockComment() { + String description = "Script without a block comment at the top."; + String category = "Test"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempScriptFileWithLines( + "//" + description, + "//@category " + category, + "class NoBlockCommentScript {", + " // just a blank class, nothing to see here", + "}"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + assertEquals(description + " \n", info.getDescription()); + + String[] actualCategory = info.getCategory(); + assertEquals(1, actualCategory.length); + assertEquals(category, actualCategory[0]); + } + + @Test + public void testJavaScriptWithSingleLineBlockComment() { + String description = "Script with a block comment at the top."; + String category = "Test"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempScriptFileWithLines( + "/* This is a test block comment. It will be ignored. */", + "//" + description, + "//@category " + category, + "class SingleLineBlockCommentScript {", + " // just a blank class, nothing to see here", + "}"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + assertEquals(description + " \n", info.getDescription()); + + String[] actualCategory = info.getCategory(); + assertEquals(1, actualCategory.length); + assertEquals(category, actualCategory[0]); + } +} diff --git a/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScriptProvider.java b/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScriptProvider.java index 7ccace6923..fe49b67279 100644 --- a/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScriptProvider.java +++ b/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScriptProvider.java @@ -16,6 +16,7 @@ package ghidra.python; import java.io.*; +import java.util.regex.Pattern; import generic.jar.ResourceFile; import ghidra.app.script.GhidraScript; @@ -23,6 +24,8 @@ import ghidra.app.script.GhidraScriptProvider; public class PythonScriptProvider extends GhidraScriptProvider { + private static final Pattern BLOCK_COMMENT = Pattern.compile("'''"); + @Override public void createNewScript(ResourceFile newScript, String category) throws IOException { PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false))); @@ -33,26 +36,31 @@ public class PythonScriptProvider extends GhidraScriptProvider { writer.close(); } + /** + * Returns a Pattern that matches block comment openings. + * In Python this is a triple single quote sequence, "'''". + * @return the Pattern for Python block comment openings + */ + @Override + public Pattern getBlockCommentStart() { + return BLOCK_COMMENT; + } + + /** + * Returns a Pattern that matches block comment closings. + * In Python this is a triple single quote sequence, "'''". + * @return the Pattern for Python block comment openings + */ + @Override + public Pattern getBlockCommentEnd() { + return BLOCK_COMMENT; + } + @Override public String getCommentCharacter() { return "#"; } - @Override - protected String getCertifyHeaderStart() { - return "## ###"; - } - - @Override - protected String getCertifyHeaderEnd() { - return "##"; - } - - @Override - protected String getCertificationBodyPrefix() { - return "#"; - } - @Override public String getDescription() { return "Python"; diff --git a/Ghidra/Features/Python/src/test.slow/java/ghidra/python/PythonScriptInfoTest.java b/Ghidra/Features/Python/src/test.slow/java/ghidra/python/PythonScriptInfoTest.java new file mode 100644 index 0000000000..f960a16b67 --- /dev/null +++ b/Ghidra/Features/Python/src/test.slow/java/ghidra/python/PythonScriptInfoTest.java @@ -0,0 +1,237 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.python; + +import static org.junit.Assert.*; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.swing.KeyStroke; + +import org.junit.*; + +import generic.jar.ResourceFile; +import ghidra.app.plugin.core.osgi.BundleHost; +import ghidra.app.script.GhidraScriptUtil; +import ghidra.app.script.ScriptInfo; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; + +public class PythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest { + + @Before + public void setUp() throws Exception { + GhidraScriptUtil.initialize(new BundleHost(), null); + Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR); + if (Files.notExists(userScriptDir)) { + Files.createDirectories(userScriptDir); + } + } + + @After + public void tearDown() throws Exception { + GhidraScriptUtil.dispose(); + } + + @Test + public void testDetailedPythonScript() { + String descLine1 = "This script exists to check that the info on"; + String descLine2 = "a script that has extensive documentation is"; + String descLine3 = "properly parsed and represented."; + String author = "Fake Name"; + String categoryTop = "Test"; + String categoryBottom = "ScriptInfo"; + String keybinding = "ctrl shift COMMA"; + String menupath = "File.Run.Detailed Script"; + String importPackage = "detailStuff"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempPyScriptFileWithLines( + "'''", + "This is a test block comment. It will be ignored.", + "@category NotTheRealCategory", + "'''", + "#" + descLine1, + "#" + descLine2, + "#" + descLine3, + "#@author " + author, + "#@category " + categoryTop + "." + categoryBottom, + "#@keybinding " + keybinding, + "#@menupath " + menupath, + "#@importpackage " + importPackage, + "print('for a blank class, it sure is well documented!')"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + + String expectedDescription = descLine1 + " \n" + descLine2 + " \n" + descLine3 + " \n"; + assertEquals(expectedDescription, info.getDescription()); + + assertEquals(author, info.getAuthor()); + assertEquals(KeyStroke.getKeyStroke(keybinding), info.getKeyBinding()); + assertEquals(menupath.replace(".", "->"), info.getMenuPathAsString()); + assertEquals(importPackage, info.getImportPackage()); + + String[] actualCategory = info.getCategory(); + assertEquals(2, actualCategory.length); + assertEquals(categoryTop, actualCategory[0]); + assertEquals(categoryBottom, actualCategory[1]); + } + + @Test + public void testPythonScriptWithBlockComment() { + String description = "Script with a block comment at the top."; + String category = "Test"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempPyScriptFileWithLines( + "'''", + "This is a test block comment. It will be ignored.", + "@category NotTheRealCategory", + "'''", + "#" + description, + "#@category " + category, + "print 'hello!'"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + assertEquals(description + " \n", info.getDescription()); + + String[] actualCategory = info.getCategory(); + assertEquals(1, actualCategory.length); + assertEquals(category, actualCategory[0]); + } + + @Test + public void testPythonScriptWithBlockCommentAndCertifyHeader() { + String description = "Script with a block comment at the top."; + String category = "Test"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempPyScriptFileWithLines( + "## ###" + + "# IP: GHIDRA" + + "# " + + "# Some license text..." + + "# you may not use this file except in compliance with the License." + + "# " + + "# blah blah blah" + + "##" + + "" + + "'''", + "This is a test block comment. It will be ignored.", + "@category NotTheRealCategory", + "'''", + "#" + description, + "#@category " + category, + "print 'hello!'"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + assertEquals(description + " \n", info.getDescription()); + + String[] actualCategory = info.getCategory(); + assertEquals(1, actualCategory.length); + assertEquals(category, actualCategory[0]); + } + + @Test + public void testPythonScriptWithoutBlockComment() { + String description = "Script without a block comment at the top."; + String category = "Test"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempPyScriptFileWithLines( + "#" + description, + "#@category " + category, + "print 'hello!'"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + assertEquals(description + " \n", info.getDescription()); + + String[] actualCategory = info.getCategory(); + assertEquals(1, actualCategory.length); + assertEquals(category, actualCategory[0]); + } + + @Test + public void testPythonScriptWithSingleLineBlockComment() { + String description = "Script with a block comment at the top."; + String category = "Test"; + ResourceFile scriptFile = null; + + try { + //@formatter:off + scriptFile = createTempPyScriptFileWithLines( + "'''This is a test block comment. It will be ignored.'''", + "#" + description, + "#@category " + category, + "print 'hello!'"); + //@formatter:on + } + catch (IOException e) { + fail("couldn't create a test script: " + e.getMessage()); + } + + ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile); + assertEquals(description + " \n", info.getDescription()); + + String[] actualCategory = info.getCategory(); + assertEquals(1, actualCategory.length); + assertEquals(category, actualCategory[0]); + } + + private ResourceFile createTempPyScriptFileWithLines(String... lines) throws IOException { + File scriptDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR); + File tempFile = File.createTempFile(testName.getMethodName(), ".py", scriptDir); + tempFile.deleteOnExit(); + ResourceFile tempResourceFile = new ResourceFile(tempFile); + + PrintWriter writer = new PrintWriter(tempResourceFile.getOutputStream()); + for (String line : lines) { + writer.println(line); + } + writer.close(); + + return tempResourceFile; + } +}