diff --git a/src/main/java/dev/jbang/source/RunContext.java b/src/main/java/dev/jbang/source/RunContext.java index 57046fb39..9d0d8ae68 100644 --- a/src/main/java/dev/jbang/source/RunContext.java +++ b/src/main/java/dev/jbang/source/RunContext.java @@ -13,6 +13,7 @@ import dev.jbang.source.resolvers.AliasResourceResolver; import dev.jbang.util.JavaUtil; import dev.jbang.util.PropertiesValueResolver; +import dev.jbang.util.Util; /** * This class contains all the extra information needed to actually run a @@ -421,6 +422,7 @@ private List allToScriptSource(List sources) { Function propsResolver = it -> PropertiesValueResolver.replaceProperties(it, getContextProperties()); return sources .stream() + .flatMap(line -> Util.explode(null, Util.getCwd(), line).stream()) .map(s -> resolveChecked(resolver, s)) .map(ref -> Source.forResourceRef(ref, propsResolver)) .collect(Collectors.toList()); diff --git a/src/main/java/dev/jbang/util/Util.java b/src/main/java/dev/jbang/util/Util.java index d74a08a5c..c321dcf35 100644 --- a/src/main/java/dev/jbang/util/Util.java +++ b/src/main/java/dev/jbang/util/Util.java @@ -188,46 +188,63 @@ static private boolean isPattern(String pattern) { } /** - * Explodes filepattern found in baseDir returnin list of relative Path names. + * Explodes filePattern found in baseDir returning list of relative Path names. * * TODO: this really should return some kind of abstraction of paths that allow * it be portable for urls as wells as files...or have a filesystem for each... * * @param source * @param baseDir - * @param filepattern + * @param filePattern * @return */ - public static List explode(String source, Path baseDir, String filepattern) { - + public static List explode(String source, Path baseDir, String filePattern) { List results = new ArrayList<>(); if (source != null && Util.isURL(source)) { // if url then just return it back for others to resolve. // TODO: technically this is really where it should get resolved! - if (isPattern(filepattern)) { - Util.warnMsg("Pattern " + filepattern + " used while using URL to run; this could result in errors."); + if (isPattern(filePattern)) { + Util.warnMsg("Pattern " + filePattern + " used while using URL to run; this could result in errors."); return results; } else { - results.add(filepattern); + results.add(filePattern); } - } else if (Util.isURL(filepattern)) { - results.add(filepattern); - } else if (!filepattern.contains("?") && !filepattern.contains("*")) { + } else if (Util.isURL(filePattern)) { + results.add(filePattern); + } else if (!filePattern.contains("?") && !filePattern.contains("*")) { // not a pattern thus just as well return path directly - results.add(filepattern); + results.add(filePattern); } else { - // it is a non-url letls try locate it - PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + filepattern); + // it is a non-url let's try to locate it + final Path bd; + final boolean useAbsPath; + Path base = basePathWithoutPattern(filePattern); + String fp; + if (base.isAbsolute()) { + bd = base; + fp = filePattern.substring(bd.toString().length() + 1); + useAbsPath = true; + } else { + bd = baseDir; + fp = filePattern; + useAbsPath = false; + } + PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + fp); FileVisitor matcherVisitor = new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) { - Path relpath = baseDir.relativize(file); + Path relpath = bd.relativize(file); if (matcher.matches(relpath)) { // to avoid windows fail. if (file.toFile().exists()) { - results.add(relpath.toString().replace("\\", "/")); + Path p = useAbsPath ? file : relpath; + if (isWindows()) { + results.add(p.toString().replace("\\", "/")); + } else { + results.add(p.toString()); + } } else { Util.verboseMsg("Warning: " + relpath + " matches but does not exist!"); } @@ -237,15 +254,31 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) { }; try { - Files.walkFileTree(baseDir, matcherVisitor); + Files.walkFileTree(bd, matcherVisitor); } catch (IOException e) { - throw new ExitException(BaseCommand.EXIT_INTERNAL_ERROR, - "Problem looking for " + filepattern + " in " + baseDir.toString(), e); + throw new ExitException(BaseCommand.EXIT_INTERNAL_ERROR, "Problem looking for " + fp + " in " + bd, e); } } return results; } + private static Path basePathWithoutPattern(String path) { + int p1 = path.indexOf('?'); + int p2 = path.indexOf('*'); + int pp = p1 < 0 ? p2 : (p2 < 0 ? p1 : Math.min(p1, p2)); + if (pp >= 0) { + String npath = Util.isWindows() ? path.replace('\\', '/') : path; + int ps = npath.lastIndexOf('/', pp); + if (ps >= 0) { + return Paths.get(path.substring(0, ps + 1)); + } else { + return Paths.get(""); + } + } else { + return Paths.get(path); + } + } + /** * @param name script name * @return camel case of kebab string if name does not end with .java or .jsh diff --git a/src/test/java/dev/jbang/cli/TestRun.java b/src/test/java/dev/jbang/cli/TestRun.java index abc111d8f..d063b2695 100644 --- a/src/test/java/dev/jbang/cli/TestRun.java +++ b/src/test/java/dev/jbang/cli/TestRun.java @@ -1547,6 +1547,52 @@ protected void runCompiler(List optionList) }.setFresh(true).build(); } + @Test + void testAdditionalSourcesGlobbing() throws IOException { + Util.setCwd(examplesTestFolder); + String mainFile = examplesTestFolder.resolve("foo.java").toString(); + String incFile = examplesTestFolder.resolve("bar/Bar.java").toString(); + + CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("build", "-s", "bar/*.java", "foo.java"); + Build build = (Build) pr.subcommand().commandSpec().userObject(); + + RunContext ctx = build.getRunContext(); + SourceSet ss = (SourceSet) ctx.forResource(mainFile); + + new JavaBuilder(ss, ctx) { + @Override + protected void runCompiler(List optionList) + throws IOException { + assertThat(optionList, hasItem(mainFile)); + assertThat(optionList, hasItem(incFile)); + // Skip the compiler + } + }.setFresh(true).build(); + } + + @Test + void testAdditionalSourcesAbsGlobbing() throws IOException { + String mainFile = examplesTestFolder.resolve("foo.java").toString(); + String incGlob = examplesTestFolder.resolve("bar").toString() + File.separatorChar + "*.java"; + String incFile = examplesTestFolder.resolve("bar/Bar.java").toString(); + + CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("build", "-s", incGlob, mainFile); + Build build = (Build) pr.subcommand().commandSpec().userObject(); + + RunContext ctx = build.getRunContext(); + SourceSet ss = (SourceSet) ctx.forResource(mainFile); + + new JavaBuilder(ss, ctx) { + @Override + protected void runCompiler(List optionList) + throws IOException { + assertThat(optionList, hasItem(mainFile)); + assertThat(optionList, hasItem(incFile)); + // Skip the compiler + } + }.setFresh(true).build(); + } + WireMockServer wms; @BeforeEach diff --git a/src/test/java/dev/jbang/util/TestUtil.java b/src/test/java/dev/jbang/util/TestUtil.java index f8756069f..dbadadbb3 100644 --- a/src/test/java/dev/jbang/util/TestUtil.java +++ b/src/test/java/dev/jbang/util/TestUtil.java @@ -92,6 +92,34 @@ void testExplode() throws IOException { } + @Test + void testExplodeAbs() throws IOException { + Path baseDir = examplesTestFolder; + + String source = "."; + String dir = examplesTestFolder.toString().replace('\\', '/'); + + final List p = Util.explode(source, cwdDir, dir + "/**.java"); + + assertThat(p, hasItem(dir + "/res/resource.java")); + assertThat(p, not(hasItem(dir + "/hello.jsh"))); + assertThat(p, hasItem(dir + "/quote.java")); + + p.clear(); + p.addAll(Util.explode(source, cwdDir, dir + "/**/*.java")); + + assertThat(p, hasItem(dir + "/res/resource.java")); + assertThat(p, not(hasItem(dir + "/quote.java"))); + assertThat(p, not(hasItem(dir + "/main.jsh"))); + + p.clear(); + p.addAll(Util.explode(source, cwdDir, dir + "/res/resource.java")); + + assertThat(p, containsInAnyOrder(dir + "/res/resource.java")); + assertThat(p, not(hasItem(dir + "/test.java"))); + + } + @Test void testDispostionFilename() { assertThat(Util.getDispositionFilename("inline; filename=token"), equalTo("token"));