diff --git a/karate-core/pom.xml b/karate-core/pom.xml
index 94e40d117..25bd7ec55 100644
--- a/karate-core/pom.xml
+++ b/karate-core/pom.xml
@@ -103,6 +103,11 @@
picocli
4.5.2
+
+ io.github.classgraph
+ classgraph
+ 4.8.90
+
org.junit.jupiter
junit-jupiter
diff --git a/karate-core/src/main/java/com/intuit/karate/resource/FileResource.java b/karate-core/src/main/java/com/intuit/karate/resource/FileResource.java
new file mode 100644
index 000000000..f9549c4e6
--- /dev/null
+++ b/karate-core/src/main/java/com/intuit/karate/resource/FileResource.java
@@ -0,0 +1,84 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2020 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+/**
+ *
+ * @author pthomas3
+ */
+public class FileResource implements Resource {
+
+ private final File file;
+ private final boolean classPath;
+ private final String relativePath;
+
+ public FileResource(File file) {
+ this(file, false, file.getPath().replace('\\', '/'));
+ }
+
+ public FileResource(File file, boolean classPath, String relativePath) {
+ this.file = file;
+ this.classPath = classPath;
+ this.relativePath = relativePath;
+ }
+
+ @Override
+ public boolean isFile() {
+ return true;
+ }
+
+ @Override
+ public File getFile() {
+ return file;
+ }
+
+ @Override
+ public boolean isClassPath() {
+ return classPath;
+ }
+
+ @Override
+ public String getRelativePath() {
+ return relativePath;
+ }
+
+ @Override
+ public InputStream getStream() {
+ try {
+ return new FileInputStream(file);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getPrefixedPath();
+ }
+
+}
diff --git a/karate-core/src/main/java/com/intuit/karate/resource/JarResource.java b/karate-core/src/main/java/com/intuit/karate/resource/JarResource.java
new file mode 100644
index 000000000..3522dbfb4
--- /dev/null
+++ b/karate-core/src/main/java/com/intuit/karate/resource/JarResource.java
@@ -0,0 +1,74 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2020 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.resource;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ *
+ * @author pthomas3
+ */
+public class JarResource implements Resource {
+
+ private final byte[] bytes;
+ private final String relativePath;
+
+ public JarResource(byte[] bytes, String relativePath) {
+ this.bytes = bytes;
+ this.relativePath = relativePath;
+ }
+
+ @Override
+ public boolean isFile() {
+ return false;
+ }
+
+ @Override
+ public boolean isClassPath() {
+ return true;
+ }
+
+ @Override
+ public File getFile() {
+ return null;
+ }
+
+ @Override
+ public String getRelativePath() {
+ return relativePath;
+ }
+
+ @Override
+ public InputStream getStream() {
+ return new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public String toString() {
+ return getPrefixedPath();
+ }
+
+}
diff --git a/karate-core/src/main/java/com/intuit/karate/resource/Resource.java b/karate-core/src/main/java/com/intuit/karate/resource/Resource.java
new file mode 100644
index 000000000..5d896fed9
--- /dev/null
+++ b/karate-core/src/main/java/com/intuit/karate/resource/Resource.java
@@ -0,0 +1,49 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2020 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.resource;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ *
+ * @author pthomas3
+ */
+public interface Resource {
+
+ boolean isFile();
+
+ boolean isClassPath();
+
+ File getFile();
+
+ String getRelativePath();
+
+ default String getPrefixedPath() {
+ return isClassPath() ? "classpath:" + getRelativePath() : getRelativePath();
+ }
+
+ InputStream getStream();
+
+}
diff --git a/karate-core/src/main/java/com/intuit/karate/resource/ResourceUtils.java b/karate-core/src/main/java/com/intuit/karate/resource/ResourceUtils.java
new file mode 100644
index 000000000..1f1d1b51c
--- /dev/null
+++ b/karate-core/src/main/java/com/intuit/karate/resource/ResourceUtils.java
@@ -0,0 +1,95 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2020 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.resource;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ResourceList;
+import io.github.classgraph.ScanResult;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author pthomas3
+ */
+public class ResourceUtils {
+
+ private static final Logger logger = LoggerFactory.getLogger(ResourceUtils.class);
+
+ private ResourceUtils() {
+ // only static methods
+ }
+
+ public static List findResourcesByExtension(String extension, String... paths) {
+ List list = new ArrayList();
+ try (ScanResult scanResult = new ClassGraph().acceptPaths(paths).scan()) {
+ ResourceList rl = scanResult.getResourcesWithExtension(extension);
+ rl.forEachByteArrayIgnoringIOException((res, bytes) -> {
+ URI uri = res.getURI();
+ if ("file".equals(uri.getScheme())) {
+ File file = Paths.get(uri).toFile();
+ list.add(new FileResource(file, true, res.getPath()));
+ } else {
+ list.add(new JarResource(bytes, res.getPath()));
+ }
+ });
+ }
+ return list;
+ }
+
+ public static List findFilesByExtension(String extension, File... files) {
+ Set results = new HashSet();
+ for (File base : files) {
+ Path searchPath = base.toPath();
+ Stream stream;
+ try {
+ stream = Files.walk(searchPath);
+ for (Iterator paths = stream.iterator(); paths.hasNext();) {
+ Path path = paths.next();
+ String fileName = path.getFileName().toString();
+ if (fileName.endsWith("." + extension)) {
+ results.add(path.toFile());
+ }
+ }
+ } catch (IOException e) { // NoSuchFileException
+ logger.trace("unable to walk path: {} - {}", searchPath, e.getMessage());
+ }
+ }
+ return results.stream().map(f -> new FileResource(f)).collect(Collectors.toList());
+ }
+
+}
diff --git a/karate-core/src/test/java/com/intuit/karate/resource/ResourceUtilsTest.java b/karate-core/src/test/java/com/intuit/karate/resource/ResourceUtilsTest.java
new file mode 100644
index 000000000..2d2e1c881
--- /dev/null
+++ b/karate-core/src/test/java/com/intuit/karate/resource/ResourceUtilsTest.java
@@ -0,0 +1,78 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2020 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.resource;
+
+import com.intuit.karate.FileUtils;
+import java.io.File;
+import java.util.List;
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author pthomas3
+ */
+class ResourceUtilsTest {
+
+ static final Logger logger = LoggerFactory.getLogger(ResourceUtilsTest.class);
+
+ @Test
+ void testFindFilesByExtension() {
+ List list = ResourceUtils.findFilesByExtension("txt", new File("src/test/java/com/intuit/karate/resource"));
+ assertEquals(1, list.size());
+ Resource resource = list.iterator().next();
+ assertTrue(resource.isFile());
+ assertFalse(resource.isClassPath());
+ assertEquals("src/test/java/com/intuit/karate/resource/test1.txt", resource.getRelativePath());
+ assertEquals("src/test/java/com/intuit/karate/resource/test1.txt", resource.getPrefixedPath());
+ assertEquals("foo", FileUtils.toString(resource.getStream()));
+ }
+
+ @Test
+ void testFindJarFilesByExtension() {
+ List list = ResourceUtils.findResourcesByExtension("properties", "cucumber");
+ assertEquals(1, list.size());
+ Resource resource = list.iterator().next();
+ assertFalse(resource.isFile());
+ assertTrue(resource.isClassPath());
+ assertEquals("cucumber/version.properties", resource.getRelativePath());
+ assertEquals("classpath:cucumber/version.properties", resource.getPrefixedPath());
+ assertEquals("cucumber-jvm.version=1.2.5", FileUtils.toString(resource.getStream()));
+ }
+
+ @Test
+ void testFindClassPathFilesByExtension() {
+ List list = ResourceUtils.findResourcesByExtension("txt", "com/intuit/karate/resource");
+ assertEquals(1, list.size());
+ Resource resource = list.iterator().next();
+ assertTrue(resource.isFile());
+ assertTrue(resource.isClassPath());
+ assertEquals("com/intuit/karate/resource/test1.txt", resource.getRelativePath());
+ assertEquals("classpath:com/intuit/karate/resource/test1.txt", resource.getPrefixedPath());
+ assertEquals("foo", FileUtils.toString(resource.getStream()));
+ }
+
+}
diff --git a/karate-core/src/test/java/com/intuit/karate/resource/test1.txt b/karate-core/src/test/java/com/intuit/karate/resource/test1.txt
new file mode 100644
index 000000000..191028156
--- /dev/null
+++ b/karate-core/src/test/java/com/intuit/karate/resource/test1.txt
@@ -0,0 +1 @@
+foo
\ No newline at end of file