Skip to content

Commit 7345f92

Browse files
committed
feat: we now support @argument-files
1 parent 186fe1b commit 7345f92

File tree

12 files changed

+743
-108
lines changed

12 files changed

+743
-108
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ target/
33
!**/src/main/**/target/
44
!**/src/test/**/target/
55
deps/
6+
classes/
67

78
### IntelliJ IDEA ###
89
.idea/

README.md

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,7 @@ But you can also simply download and unzip the [release package](https://github.
174174
175175
See:
176176
177-
```shell
178-
$ jpm --help
177+
```
179178
Usage: jpm [-hV] [COMMAND]
180179
Simple command line tool for managing Maven artifacts
181180
-h, --help Show this help message and exit.
@@ -217,12 +216,48 @@ Commands:
217216
Example:
218217
jpm path org.apache.httpcomponents:httpclient:4.5.14
219218
220-
do Executes an action command defined in the app.yml file. Actions
221-
can use variable substitution for classpath.
219+
exec Executes a shell command that can use special tokens to deal with
220+
OS-specific quirks like paths. This means that commands can be
221+
written in a somewhat platform independent way and will work on
222+
Windows, Linux and MacOS.
223+
224+
Supported tokens and what they expand to:
225+
{{deps}} : the classpath of all dependencies defined in the app.yml file
226+
{/} : the OS' file path separator
227+
{:} : the OS' class path separator
228+
{~} : the user's home directory using the OS' class path format
229+
{;} : the OS' command separator
230+
{./file/path} : a path using the OS' path format (must start with './'!)
231+
{./lib:./ext} : a class path using the OS' class path format (must start with './'!)
232+
@[ ... ] : writes contents to a file and inserts @<path-to-file> instead
233+
234+
In actuality the command is pretty smart and will try to do the
235+
right thing, as long as {{deps}} is the only token you use. In
236+
the examples below the first line shows how to do it the hard
237+
way, by specifying everything manually, while the second line
238+
shows how much easier it is when you can rely on the built-in
239+
smart feature. Is the smart feature bothering you? Just use any
240+
of the other tokens besides {{deps}} and it will be turned off.
241+
By default args files will only be considered for Java commands
242+
that are know to support them (java, javac, javadoc, etc), but
243+
you can indicate that your command supports it as well by
244+
adding a single @ as the first character of the command.
222245
246+
Example:
247+
jpm exec javac -cp @[{{deps}}] -d {./out/classes} --source-path {./src/main/java} App.java
248+
jpm exec javac -cp {{deps}} -d out/classes --source-path src/main/java App.java
249+
jpm exec @kotlinc -cp {{deps}} -d out/classes src/main/kotlin/App.kt
250+
251+
do Executes an action command defined in the app.yml file. The
252+
command is executed using the same rules as the exec command,
253+
so it can use all the same tokens and features. You can also
254+
pass additional arguments to the action using -a or --arg
255+
followed by the argument value. You can chain multiple actions
256+
and their arguments in a single command line.
223257
Example:
224258
jpm do build
225-
jpm do test
259+
jpm do test --arg verbose
260+
jpm do build -a --fresh test -a verbose
226261
227262
clean Executes the 'clean' action as defined in the app.yml file.
228263
build Executes the 'build' action as defined in the app.yml file.

app.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ actions:
1313
build: ".{/}mvnw spotless:apply package -DskipTests"
1414
run: "{./target/binary/bin/jpm}"
1515
test: ".{/}mvnw test"
16+
buildj: "javac -cp {{deps}} -d classes --source-path src/main/java src/main/java/org/codejive/jpm/Main.java"
17+
runj: "java -cp classes:{{deps}} org.codejive.jpm.Main"

misc/grammar.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
commands ::= <elem>+
2+
elem ::= <group> | <separator> | <command>
3+
separator ::= ';' | '&&' | '||'
4+
group ::= '(' <commands> ')'
5+
command ::= <word>+

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.IOException;
44
import java.nio.file.Path;
55
import java.util.*;
6+
import java.util.stream.Collectors;
67
import org.codejive.jpm.config.AppInfo;
78
import org.codejive.jpm.util.*;
89
import org.eclipse.aether.artifact.Artifact;
@@ -240,7 +241,7 @@ private Map<String, String> getRepositories(Map<String, String> extraRepos, AppI
240241
/**
241242
* Executes an action defined in app.yml file.
242243
*
243-
* @param actionName The name of the action to execute (null to list actions)
244+
* @param actionName The name of the action to execute
244245
* @return An integer containing the exit result of the action
245246
* @throws IllegalArgumentException If the action name is not provided or not found
246247
* @throws IOException If an error occurred during the operation
@@ -260,13 +261,15 @@ public int executeAction(String actionName, List<String> args)
260261
+ "' not found in app.yml. Use --list to see available actions.");
261262
}
262263

263-
// Get the classpath for variable substitution only if needed
264-
List<Path> classpath = Collections.emptyList();
265-
if (command.contains("{{deps}}")) {
266-
classpath = this.path(new String[0]); // Empty array means use dependencies from app.yml
264+
// Add the user arguments to the command
265+
if (args != null && !args.isEmpty()) {
266+
command +=
267+
args.stream()
268+
.map(ScriptUtils::quoteArgument)
269+
.collect(Collectors.joining(" ", " ", ""));
267270
}
268271

269-
return ScriptUtils.executeScript(command, args, classpath, verbose);
272+
return executeCommand(command);
270273
}
271274

272275
/**
@@ -279,4 +282,24 @@ public List<String> listActions() throws IOException {
279282
AppInfo appInfo = AppInfo.read();
280283
return new ArrayList<>(appInfo.getActionNames());
281284
}
285+
286+
/**
287+
* Executes an action defined in app.yml file.
288+
*
289+
* @param command The command to execute
290+
* @return An integer containing the exit result of the action
291+
* @throws IOException If an error occurred during the operation
292+
* @throws DependencyResolutionException If an error occurred during dependency resolution
293+
* @throws InterruptedException If the action execution was interrupted
294+
*/
295+
public int executeCommand(String command)
296+
throws IOException, DependencyResolutionException, InterruptedException {
297+
// Get the classpath for variable substitution only if needed
298+
List<Path> classpath = Collections.emptyList();
299+
if (command.contains("{{deps}}")) {
300+
classpath = this.path(new String[0]); // Empty array means use dependencies from app.yml
301+
}
302+
303+
return ScriptUtils.executeScript(command, classpath, verbose);
304+
}
282305
}

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

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
//DEPS org.yaml:snakeyaml:2.4
55
//DEPS org.jline:jline-console-ui:3.30.5 org.jline:jline-terminal-jni:3.30.5
66
//DEPS org.slf4j:slf4j-api:2.0.17 org.slf4j:slf4j-simple:2.0.17
7-
//SOURCES Jpm.java config/AppInfo.java util/FileUtils.java util/Resolver.java util/ScriptUtils.java
8-
//SOURCES util/SearchResult.java util/SearchUtils.java util/SyncStats.java util/Version.java
7+
//SOURCES Jpm.java config/AppInfo.java util/CommandsParser.java util/FileUtils.java util/Resolver.java
8+
//SOURCES util/ScriptUtils.java util/SearchResult.java util/SearchUtils.java util/SyncStats.java util/Version.java
99
// spotless:on
1010

1111
package org.codejive.jpm;
@@ -49,6 +49,7 @@
4949
Main.Search.class,
5050
Main.Install.class,
5151
Main.PrintPath.class,
52+
Main.Exec.class,
5253
Main.Do.class,
5354
Main.Clean.class,
5455
Main.Build.class,
@@ -331,11 +332,67 @@ public Integer call() throws Exception {
331332
}
332333
}
333334

335+
@Command(
336+
name = "exec",
337+
description =
338+
"Executes a shell command that can use special tokens to deal with OS-specific quirks like paths."
339+
+ " This means that commands can be written in a somewhat platform independent way and will work on Windows, Linux and MacOS.\n"
340+
+ "\n"
341+
+ "Supported tokens and what they expand to:\n"
342+
+ " {{deps}} : the classpath of all dependencies defined in the app.yml file\n"
343+
+ " {/} : the OS' file path separator\n"
344+
+ " {:} : the OS' class path separator\n"
345+
+ " {~} : the user's home directory using the OS' class path format\n"
346+
+ " {;} : the OS' command separator\n"
347+
+ " {./file/path} : a path using the OS' path format (must start with './'!)\n"
348+
+ " {./lib:./ext} : a class path using the OS' class path format (must start with './'!)\n"
349+
+ " @[ ... ] : writes contents to a file and inserts @<path-to-file> instead\n"
350+
+ "\n"
351+
+ "In actuality the command is pretty smart and will try to do the right thing, as long as {{deps}} is the only token you use."
352+
+ " In the examples below the first line shows how to do it the hard way, by specifying everything manually, while the second line shows how much easier it is when you can rely on the built-in smart feature."
353+
+ " Is the smart feature bothering you? Just use any of the other tokens besides {{deps}} and it will be turned off."
354+
+ " By default args files will only be considered for Java commands that are know to support them (java, javac, javadoc, etc), but you can indicate that your command supports it as well by adding a single @ as the first character of the command.\n"
355+
+ "\n"
356+
+ "Example:\n"
357+
+ " jpm exec javac -cp @[{{deps}}] -d {./out/classes} --source-path {./src/main/java} App.java\n"
358+
+ " jpm exec javac -cp {{deps}} -d out/classes --source-path src/main/java App.java\n"
359+
+ " jpm exec @kotlinc -cp {{deps}} -d out/classes src/main/kotlin/App.kt\n")
360+
static class Exec implements Callable<Integer> {
361+
@Mixin DepsMixin depsMixin;
362+
@Mixin QuietMixin quietMixin;
363+
364+
@Parameters(paramLabel = "command", description = "The command to execute", arity = "0..*")
365+
private List<String> command;
366+
367+
@Override
368+
public Integer call() throws Exception {
369+
String cmd = String.join(" ", command);
370+
try {
371+
return Jpm.builder()
372+
.directory(depsMixin.directory)
373+
.noLinks(depsMixin.noLinks)
374+
.verbose(!quietMixin.quiet)
375+
.build()
376+
.executeCommand(cmd);
377+
} catch (Exception e) {
378+
System.err.println(e.getMessage());
379+
return 1;
380+
}
381+
}
382+
}
383+
334384
@Command(
335385
name = "do",
336386
description =
337-
"Executes an action command defined in the app.yml file.\n\n"
338-
+ "Example:\n jpm do build\n jpm do test --arg verbose\n")
387+
"Executes an action command defined in the app.yml file."
388+
+ " The command is executed using the same rules as the exec command, so it can use all the same tokens and features."
389+
+ " You can also pass additional arguments to the action using -a or --arg followed by the argument value."
390+
+ " You can chain multiple actions and their arguments in a single command line."
391+
+ "\n"
392+
+ "Example:\n"
393+
+ " jpm do build\n"
394+
+ " jpm do test --arg verbose\n"
395+
+ " jpm do build -a --fresh test -a verbose\n")
339396
static class Do implements Callable<Integer> {
340397
@Mixin DepsMixin depsMixin;
341398
@Mixin QuietMixin quietMixin;

src/main/java/org/codejive/jpm/config/AppInfo.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public java.util.Set<String> getActionNames() {
6161
* @return An instance of AppInfo
6262
* @throws IOException if an error occurred while reading or parsing the file
6363
*/
64+
@SuppressWarnings("unchecked")
6465
public static AppInfo read() throws IOException {
6566
Path prjJson = Paths.get(System.getProperty("user.dir"), APP_INFO_FILE);
6667
AppInfo appInfo = new AppInfo();
@@ -106,6 +107,7 @@ public static AppInfo read() throws IOException {
106107
* @param appInfo The AppInfo object to write
107108
* @throws IOException if an error occurred while writing the file
108109
*/
110+
@SuppressWarnings("unchecked")
109111
public static void write(AppInfo appInfo) throws IOException {
110112
Path prjJson = Paths.get(System.getProperty("user.dir"), APP_INFO_FILE);
111113
try (Writer out = Files.newBufferedWriter(prjJson)) {

0 commit comments

Comments
 (0)