Skip to content

Commit c652724

Browse files
committed
Added complete path replacement in actions
1 parent 1a2632a commit c652724

File tree

8 files changed

+136
-34
lines changed

8 files changed

+136
-34
lines changed

app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ dependencies:
1111
actions:
1212
clean: ".{/}mvnw clean"
1313
build: ".{/}mvnw spotless:apply package -DskipTests"
14-
run: "java -jar target{/}jpm-0.4.1-cli.jar"
14+
run: "{./target/binary/bin/jpm}"
1515
test: ".{/}mvnw test"

src/main/java/org/codejive/jpm/Jpm.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
public class Jpm {
1313
private final Path directory;
1414
private final boolean noLinks;
15+
private final boolean verbose;
1516

16-
private Jpm(Path directory, boolean noLinks) {
17+
private Jpm(Path directory, boolean noLinks, boolean verbose) {
1718
this.directory = directory;
1819
this.noLinks = noLinks;
20+
this.verbose = verbose;
1921
}
2022

2123
/**
@@ -31,6 +33,7 @@ public static Builder builder() {
3133
public static class Builder {
3234
private Path directory;
3335
private boolean noLinks;
36+
private boolean verbose;
3437

3538
private Builder() {}
3639

@@ -56,13 +59,24 @@ public Builder noLinks(boolean noLinks) {
5659
return this;
5760
}
5861

62+
/**
63+
* Set whether to enable verbose output or not.
64+
*
65+
* @param verbose Whether to enable verbose output or not.
66+
* @return The builder instance for chaining.
67+
*/
68+
public Builder verbose(boolean verbose) {
69+
this.verbose = verbose;
70+
return this;
71+
}
72+
5973
/**
6074
* Builds the {@link Jpm} instance.
6175
*
6276
* @return A {@link Jpm} instance.
6377
*/
6478
public Jpm build() {
65-
return new Jpm(directory, noLinks);
79+
return new Jpm(directory, noLinks, verbose);
6680
}
6781
}
6882

@@ -196,7 +210,7 @@ public int executeAction(String actionName, List<String> args)
196210
classpath = this.path(new String[0]); // Empty array means use dependencies from app.yml
197211
}
198212

199-
return ScriptUtils.executeScript(command, args, classpath);
213+
return ScriptUtils.executeScript(command, args, classpath, true);
200214
}
201215

202216
/**

src/main/java/org/codejive/jpm/Main.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ public Integer call() throws Exception {
324324
+ "Example:\n jpm do build\n jpm do test\n")
325325
static class Do implements Callable<Integer> {
326326
@Mixin DepsMixin depsMixin;
327+
@Mixin QuietMixin quietMixin;
327328

328329
@Option(
329330
names = {"-l", "--list"},
@@ -357,9 +358,9 @@ public Integer call() throws Exception {
357358
.build()
358359
.listActions();
359360
if (actionNames.isEmpty()) {
360-
System.out.println("No actions defined in app.yml");
361+
if (!quietMixin.quiet) System.out.println("No actions defined in app.yml");
361362
} else {
362-
System.out.println("Available actions:");
363+
if (!quietMixin.quiet) System.out.println("Available actions:");
363364
actionNames.forEach(n -> System.out.println(" " + n));
364365
}
365366
} else {
@@ -398,6 +399,7 @@ public Integer call() throws Exception {
398399
Jpm.builder()
399400
.directory(depsMixin.directory)
400401
.noLinks(depsMixin.noLinks)
402+
.verbose(!quietMixin.quiet)
401403
.build()
402404
.executeAction(action, args);
403405
if (exitCode != 0) {

src/main/java/org/codejive/jpm/util/ScriptUtils.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import java.io.IOException;
66
import java.io.InputStreamReader;
77
import java.nio.file.Path;
8+
import java.nio.file.Paths;
9+
import java.util.Arrays;
810
import java.util.List;
911
import java.util.Locale;
1012
import java.util.stream.Collectors;
@@ -16,13 +18,15 @@ public class ScriptUtils {
1618
* Executes a script command with variable substitution and path conversion.
1719
*
1820
* @param command The command to execute
19-
* @param args
21+
* @param args The arguments to pass to the command
2022
* @param classpath The classpath to use for {{deps}} substitution
23+
* @param verbose If true, prints the command before execution
2124
* @return The exit code of the executed command
2225
* @throws IOException if an error occurred during execution
2326
* @throws InterruptedException if the execution was interrupted
2427
*/
25-
public static int executeScript(String command, List<String> args, List<Path> classpath)
28+
public static int executeScript(
29+
String command, List<String> args, List<Path> classpath, boolean verbose)
2630
throws IOException, InterruptedException {
2731
if (args != null && !args.isEmpty()) {
2832
command +=
@@ -32,6 +36,9 @@ public static int executeScript(String command, List<String> args, List<Path> cl
3236
}
3337

3438
String processedCommand = processCommand(command, classpath);
39+
if (verbose) {
40+
System.out.println("> " + processedCommand);
41+
}
3542
String[] commandTokens =
3643
isWindows()
3744
? new String[] {"cmd.exe", "/c", processedCommand}
@@ -69,8 +76,46 @@ static String processCommand(String command, List<Path> classpath) {
6976
}
7077
result = result.replace("{{deps}}", classpathStr);
7178
}
79+
80+
// Find all occurrences of {./...} and {~/...} and replace them with os paths
81+
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\{([.~]/[^}]*)}");
82+
java.util.regex.Matcher matcher = pattern.matcher(result);
83+
StringBuilder sb = new StringBuilder();
84+
while (matcher.find()) {
85+
String path = matcher.group(1);
86+
String replacedPath;
87+
if (isWindows()) {
88+
String[] cp = path.split(":");
89+
replacedPath =
90+
Arrays.stream(cp)
91+
.map(
92+
p -> {
93+
if (p.startsWith("~/")) {
94+
return Paths.get(
95+
System.getProperty("user.home"),
96+
p.substring(2))
97+
.toString();
98+
} else {
99+
return Paths.get(p).toString();
100+
}
101+
})
102+
.collect(Collectors.joining(File.pathSeparator));
103+
replacedPath = replacedPath.replace("\\", "\\\\");
104+
} else {
105+
// If we're not on Windows, we assume the path is already correct
106+
replacedPath = path;
107+
}
108+
matcher.appendReplacement(sb, replacedPath);
109+
}
110+
matcher.appendTail(sb);
111+
result = sb.toString();
112+
72113
result = result.replace("{/}", File.separator);
73114
result = result.replace("{:}", File.pathSeparator);
115+
result =
116+
result.replace(
117+
"{~}",
118+
isWindows() ? Paths.get(System.getProperty("user.home")).toString() : "~");
74119

75120
return result;
76121
}

src/test/java/org/codejive/jpm/DoCommandPerformanceTest.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ void testPerformanceOptimizationNoDepsVariable() throws IOException {
4747
// Mock ScriptUtils to verify classpath is empty when no {{deps}} variable
4848
try (MockedStatic<ScriptUtils> mockedScriptUtils = Mockito.mockStatic(ScriptUtils.class)) {
4949
mockedScriptUtils
50-
.when(() -> ScriptUtils.executeScript(anyString(), any(List.class), any()))
50+
.when(
51+
() ->
52+
ScriptUtils.executeScript(
53+
anyString(), any(List.class), any(), anyBoolean()))
5154
.thenReturn(0);
5255

5356
CommandLine cmd = Main.getCommandLine();
@@ -61,7 +64,8 @@ void testPerformanceOptimizationNoDepsVariable() throws IOException {
6164
ScriptUtils.executeScript(
6265
eq("true"),
6366
eq(Collections.emptyList()),
64-
eq(Collections.emptyList())),
67+
eq(Collections.emptyList()),
68+
eq(true)),
6569
times(1));
6670
}
6771
}
@@ -78,7 +82,10 @@ void testCaseInsensitiveDepsVariable() throws IOException {
7882

7983
try (MockedStatic<ScriptUtils> mockedScriptUtils = Mockito.mockStatic(ScriptUtils.class)) {
8084
mockedScriptUtils
81-
.when(() -> ScriptUtils.executeScript(anyString(), any(List.class), any()))
85+
.when(
86+
() ->
87+
ScriptUtils.executeScript(
88+
anyString(), any(List.class), any(), anyBoolean()))
8289
.thenReturn(0);
8390

8491
CommandLine cmd = Main.getCommandLine();
@@ -88,7 +95,10 @@ void testCaseInsensitiveDepsVariable() throws IOException {
8895
mockedScriptUtils.verify(
8996
() ->
9097
ScriptUtils.executeScript(
91-
contains("{{deps}}"), eq(Collections.emptyList()), any()),
98+
contains("{{deps}}"),
99+
eq(Collections.emptyList()),
100+
any(),
101+
eq(true)),
92102
times(1));
93103

94104
mockedScriptUtils.clearInvocations();
@@ -100,7 +110,8 @@ void testCaseInsensitiveDepsVariable() throws IOException {
100110
ScriptUtils.executeScript(
101111
eq("java -cp ${DEPS} MainClass"),
102112
eq(Collections.emptyList()),
103-
eq(Collections.emptyList())),
113+
eq(Collections.emptyList()),
114+
eq(true)),
104115
times(1));
105116

106117
mockedScriptUtils.clearInvocations();
@@ -112,7 +123,8 @@ void testCaseInsensitiveDepsVariable() throws IOException {
112123
ScriptUtils.executeScript(
113124
eq("java -cp mydeps MainClass"),
114125
eq(Collections.emptyList()),
115-
eq(Collections.emptyList())),
126+
eq(Collections.emptyList()),
127+
eq(true)),
116128
times(1));
117129
}
118130
}

src/test/java/org/codejive/jpm/MainIntegrationTest.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ void testDoCommandListShortFlag() throws IOException {
105105

106106
assertThat(exitCode).isEqualTo(0);
107107
String output = capture.getOut();
108-
assertThat(output.contains("Available actions:")).isTrue();
108+
assertThat(output).contains("Available actions:");
109109
}
110110
}
111111

@@ -120,7 +120,7 @@ void testDoCommandListNoActions() throws IOException {
120120

121121
assertThat(exitCode).isEqualTo(0);
122122
String output = capture.getOut();
123-
assertThat(output.contains("No actions defined in app.yml")).isTrue();
123+
assertThat(output).contains("No actions defined in app.yml");
124124
}
125125
}
126126

@@ -133,7 +133,7 @@ void testDoCommandListNoAppYml() {
133133

134134
assertThat(exitCode).isEqualTo(0);
135135
String output = capture.getOut();
136-
assertThat(output.contains("No actions defined in app.yml")).isTrue();
136+
assertThat(output).contains("No actions defined in app.yml");
137137
}
138138
}
139139

@@ -147,8 +147,8 @@ void testDoCommandMissingActionName() throws IOException {
147147

148148
assertThat(exitCode).isEqualTo(1);
149149
String errorOutput = capture.getErr();
150-
assertThat(errorOutput.contains("Action name is required")).isTrue();
151-
assertThat(errorOutput.contains("Use --list to see available actions")).isTrue();
150+
assertThat(errorOutput)
151+
.contains("Action name is required", "Use --list to see available actions");
152152
}
153153
}
154154

@@ -162,10 +162,9 @@ void testDoCommandNonexistentAction() throws IOException {
162162

163163
assertThat(exitCode).isEqualTo(1);
164164
String errorOutput = capture.getErr();
165-
assertThat(
166-
errorOutput.contains(
167-
"Action 'nonexistent' not found in app.yml. Use --list to see available actions."))
168-
.isTrue();
165+
assertThat(errorOutput)
166+
.contains(
167+
"Action 'nonexistent' not found in app.yml. Use --list to see available actions.");
169168
}
170169
}
171170

@@ -215,7 +214,7 @@ void testAliasWithNonexistentAction() throws IOException {
215214
// Should fail with exit code 1 when action is not found
216215
assertThat(exitCode).isEqualTo(1);
217216
String errorOutput = capture.getErr();
218-
assertThat(errorOutput.contains("Action 'build' not found in app.yml")).isTrue();
217+
assertThat(errorOutput).contains("Action 'build' not found in app.yml");
219218
}
220219
}
221220

@@ -230,7 +229,7 @@ void testDoWithOutput() throws IOException {
230229
int exitCode = cmd.execute("do", "hello");
231230
assertThat(exitCode >= 0).isTrue(); // Should not be negative (internal error)
232231
String output = capture.getOut();
233-
assertThat(output.contains("Hello World")).isTrue();
232+
assertThat(output).contains("Hello World");
234233
}
235234
}
236235

@@ -256,12 +255,11 @@ void testDoAliasWithArgs() throws IOException {
256255
assertThat(exitCode).isEqualTo(0);
257256
String output = capture.getOut();
258257
// The run action should execute and include the classpath in the output
259-
assertThat(output).contains("running... .").contains("libs").contains("--foo bar");
258+
assertThat(output).contains("running... .", "libs", "--foo bar");
260259
}
261260
}
262261

263262
private void createAppYml() throws IOException {
264-
// Use platform-specific command for simple action that works on both Windows and Unix
265263
String yamlContent =
266264
"dependencies:\n"
267265
+ " com.github.lalyos:jfiglet: \"0.0.9\"\n"
@@ -280,7 +278,6 @@ private void createAppYmlWithoutActions() throws IOException {
280278
}
281279

282280
private void createAppYmlWithoutBuildAction() throws IOException {
283-
// Use platform-specific command for simple action that works on both Windows and Unix
284281
String yamlContent =
285282
"dependencies:\n"
286283
+ " com.github.lalyos:jfiglet: \"0.0.9\"\n"

src/test/java/org/codejive/jpm/json/AppInfoTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ void testReadAppInfoWithActions() throws IOException {
5252

5353
// Test dependencies are still parsed correctly
5454
assertThat(appInfo.dependencies).hasSize(1);
55-
assertThat(appInfo.dependencies).containsKey("com.example:test-lib");
56-
assertThat(appInfo.dependencies.get("com.example:test-lib")).isEqualTo("1.0.0");
55+
assertThat(appInfo.dependencies).containsEntry("com.example:test-lib", "1.0.0");
5756
} finally {
5857
System.setProperty("user.dir", originalDir);
5958
}

src/test/java/org/codejive/jpm/util/ScriptUtilsTest.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,51 @@ void testProcessCommandWithoutDepsSubstitution() throws Exception {
4646
@Test
4747
void testProcessCommandWithEmptyClasspath() throws Exception {
4848
List<Path> classpath = Collections.emptyList();
49-
String command = "java -cp {{deps}} MainClass";
49+
String command = "java -cp \"{{deps}}\" MainClass";
5050
String result = ScriptUtils.processCommand(command, classpath);
5151
// {{deps}} should be replaced with empty string
52-
assertThat(result).isEqualTo("java -cp MainClass");
52+
assertThat(result).isEqualTo("java -cp \"\" MainClass");
5353
}
5454

5555
@Test
5656
void testProcessCommandWithNullClasspath() throws Exception {
57-
String command = "java -cp {{deps}} MainClass";
57+
String command = "java -cp \"{{deps}}\" MainClass";
5858
String result = ScriptUtils.processCommand(command, null);
5959
// {{deps}} should be replaced with empty string
60-
assertThat(result).isEqualTo("java -cp MainClass");
60+
assertThat(result).isEqualTo("java -cp \"\" MainClass");
61+
}
62+
63+
@Test
64+
void testProcessCommandWithPathTokens() throws Exception {
65+
String command = "java -cp .{/}libs{:}.{/}ext{:}{~}{/}usrlibs MainClass";
66+
String result = ScriptUtils.processCommand(command, null);
67+
String expectedPath =
68+
ScriptUtils.isWindows()
69+
? ".\\libs;.\\ext;" + System.getProperty("user.home") + "\\usrlibs"
70+
: "./libs:./ext:~/usrlibs";
71+
assertThat(result).isEqualTo("java -cp " + expectedPath + " MainClass");
72+
}
73+
74+
@Test
75+
void testProcessCommandWithPathReplacement() throws Exception {
76+
String command = "java -cp {./libs:./ext:~/usrlibs} MainClass";
77+
String result = ScriptUtils.processCommand(command, null);
78+
String expectedPath =
79+
ScriptUtils.isWindows()
80+
? ".\\libs;.\\ext;" + System.getProperty("user.home") + "\\usrlibs"
81+
: "./libs:./ext:~/usrlibs";
82+
assertThat(result).isEqualTo("java -cp " + expectedPath + " MainClass");
83+
}
84+
85+
@Test
86+
void testProcessCommandWithPathReplacement2() throws Exception {
87+
String command = "java -cp {~/usrlibs:./libs:./ext} MainClass";
88+
String result = ScriptUtils.processCommand(command, null);
89+
String expectedPath =
90+
ScriptUtils.isWindows()
91+
? System.getProperty("user.home") + "\\usrlibs;.\\libs;.\\ext"
92+
: "~/usrlibs:./libs:./ext";
93+
assertThat(result).isEqualTo("java -cp " + expectedPath + " MainClass");
6194
}
6295

6396
@Test

0 commit comments

Comments
 (0)