Skip to content

Commit 0d349ad

Browse files
committed
more refinements
1 parent 8be5152 commit 0d349ad

4 files changed

Lines changed: 165 additions & 81 deletions

File tree

README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Just Args for Java [![tests](https://github.com/sigpwned/just-args-java/actions/workflows/tests.yml/badge.svg)](https://github.com/sigpwned/just-args-java/actions/workflows/tests.yml) [![Maven Central Version](https://img.shields.io/maven-central/v/com.sigpwned/just-args)](https://central.sonatype.com/artifact/com.sigpwned/just-args) [![javadoc](https://javadoc.io/badge2/com.sigpwned/just-args/javadoc.svg)](https://javadoc.io/doc/com.sigpwned/just-args)
2+
3+
Just Args is a small, simple library for Java that provides command-line argument parsing support and nothing else.
4+
5+
## Goals
6+
7+
Just Args should...
8+
9+
* **Parse arguments**. The library parses valid command-line arguments into a structured and useful model.
10+
* **Be very small**. The JAR file is currently less than 10KB compressed, under 25KB uncompressed.
11+
* **Be very simple**. Users only need one method to parse arguments: `JustArgs.parseArgs`.
12+
* **Be flexible**. Supports options, flags, and positional arguments, as well as advanced configurations.
13+
* **Work out of the box**. Designed to handle common argument parsing use cases with minimal configuration.
14+
15+
## Non-Goals
16+
17+
Just Args should not...
18+
19+
* **Validate command-line usage**. The library is not a strict validator and assumes you know how your CLI should behave.
20+
* **Provide advanced features**. The library intentionally avoids dependencies, complex argument validation, and advanced frameworks.
21+
22+
## Installation
23+
24+
Just Args is available in Maven Central. You can add it to your project using the following Maven dependency:
25+
26+
```xml
27+
<dependency>
28+
<groupId>com.sigpwned</groupId>
29+
<artifactId>just-args</artifactId>
30+
<version>0.0.0</version>
31+
</dependency>
32+
```
33+
34+
Just Args is a single Java file with no dependencies. In a pinch, you can copy-paste it into your project.
35+
36+
## Quickstart
37+
38+
### Basic Usage
39+
40+
To parse a list of command-line arguments:
41+
42+
```java
43+
import com.sigpwned.just.args.JustArgs;
44+
45+
List<String> args = List.of("--xray", "value1", "-f", "positional1");
46+
int maxArgs = 1;
47+
48+
Map<Character, String> shortOptionNames = Map.of('x', "xray");
49+
Map<String, String> longOptionNames = Map.of("xray", "xray");
50+
Map<Character, String> shortPositiveFlagNames = Map.of('f', "flag");
51+
Map<String, String> longPositiveFlagNames = Map.of();
52+
Map<Character, String> shortNegativeFlagNames = Map.of();
53+
Map<String, String> longNegativeFlagNames = Map.of();
54+
55+
JustArgs.ParsedArgs result = JustArgs.parseArgs(
56+
args, maxArgs, shortOptionNames, longOptionNames,
57+
shortPositiveFlagNames, longPositiveFlagNames,
58+
shortNegativeFlagNames, longNegativeFlagNames
59+
);
60+
61+
System.out.println(result.getArgs()); // [positional1]
62+
System.out.println(result.getOptions()); // {xray=[value1]}
63+
System.out.println(result.getFlags()); // {flag=[true]}
64+
```
65+
66+
### Features
67+
68+
Just Args supports:
69+
70+
* **Options**: Arguments with values, e.g., `-k value`, `--key value` or `--key=value`.
71+
* **Flags**: Boolean arguments, e.g., `-f` or `--flag`.
72+
* **Short Flag Batches**: Multiple short flags grouped together, e.g., `-abc` is equivialent to `-a -b -c`
73+
* **Positional Arguments**: Unlabeled arguments.
74+
* **Separator Token `--`**: Marks all subsequent arguments as positional.
75+
76+
---
77+
78+
## Advanced Usage
79+
80+
### Handling Syntax Errors
81+
82+
Just Args throws a `JustArgs.IllegalSyntaxException` when it encounters invalid syntax. This is a subclass of `IllegalArgumentException` for simplicity.
83+
84+
```java
85+
try {
86+
JustArgs.parseArgs(...);
87+
} catch (JustArgs.IllegalSyntaxException e) {
88+
System.err.println("Syntax error at index " + e.getIndex() + ": " + e.getMessage());
89+
}
90+
```
91+
92+
### Customizing Argument Names
93+
94+
You can configure short and long names for options and flags, and assign them to the same logical bucket in the result:
95+
96+
```java
97+
Map<Character, String> shortOptionNames = Map.of('o', "output");
98+
Map<String, String> longOptionNames = Map.of("output", "output");
99+
100+
Map<Character, String> shortPositiveFlagNames = Map.of('v', "verbose");
101+
Map<String, String> longPositiveFlagNames = Map.of("verbose", "verbose");
102+
```
103+
104+
---
105+
106+
## FAQ
107+
108+
### Why Another Argument Parsing Library?
109+
110+
Most libraries are either too large, too complex, or depend on external frameworks. Just Args is small, simple, and dependency-free—perfect for lightweight projects.
111+
112+
### What About Error Messages?
113+
114+
Just Args focuses on simplicity. Error messages are provided through exceptions, leaving full control to the user.
115+
116+
### Can You Add Feature X?
117+
118+
Feel free to ask, but probably not. Just Args is intentionally minimal. If you need advanced features, consider a more fully-featured library like Apache Commons CLI or Picocli.
119+
120+
---
121+
122+
## A Note on Development
123+
124+
Just Args was built with simplicity and clarity in mind. The library is intentionally small and avoids external dependencies to make it easy to embed in any project.
125+
126+
---

src/license/licenses.properties

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
cc0_v1 = Creative Commons Zero v1.0 Universal
21
unlicense = Unlicense

src/main/java/com/sigpwned/just/args/JustArgs.java

Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -62,40 +62,30 @@ private JustArgs() {}
6262
*/
6363
public static class ParsedArgs {
6464
private final List<String> args;
65-
private final List<String> varargs;
6665
private final Map<String, List<String>> options;
6766
private final Map<String, List<Boolean>> flags;
6867

6968
/**
7069
* Constructs a new ParsedArgs object.
7170
*
72-
* @param args the positional arguments (up to maxArgs in number)
73-
* @param varargs the "overflow" positional arguments (those beyond maxArgs)
71+
* @param args the positional arguments
7472
* @param options a map from option-name to a list of string values
7573
* @param flags a map from flag-name to a list of boolean values
7674
*/
77-
public ParsedArgs(List<String> args, List<String> varargs, Map<String, List<String>> options,
75+
public ParsedArgs(List<String> args, Map<String, List<String>> options,
7876
Map<String, List<Boolean>> flags) {
7977
this.args = unmodifiableList(args);
80-
this.varargs = unmodifiableList(varargs);
8178
this.options = unmodifiableMapOfLists(options);
8279
this.flags = unmodifiableMapOfLists(flags);
8380
}
8481

8582
/**
86-
* Returns the list of positional arguments (up to the specified maxArgs).
83+
* Returns the list of positional arguments
8784
*/
8885
public List<String> getArgs() {
8986
return args;
9087
}
9188

92-
/**
93-
* Returns the list of overflow positional arguments (those beyond maxArgs).
94-
*/
95-
public List<String> getVarargs() {
96-
return varargs;
97-
}
98-
9989
/**
10090
* Returns the map of options. Keys are option names; values are the list of values provided for
10191
* that option.
@@ -114,7 +104,7 @@ public Map<String, List<Boolean>> getFlags() {
114104

115105
@Override
116106
public int hashCode() {
117-
return Objects.hash(args, flags, options, varargs);
107+
return Objects.hash(args, flags, options);
118108
}
119109

120110
@Override
@@ -127,13 +117,12 @@ public boolean equals(Object obj) {
127117
return false;
128118
ParsedArgs other = (ParsedArgs) obj;
129119
return Objects.equals(args, other.args) && Objects.equals(flags, other.flags)
130-
&& Objects.equals(options, other.options) && Objects.equals(varargs, other.varargs);
120+
&& Objects.equals(options, other.options);
131121
}
132122

133123
@Override
134124
public String toString() {
135-
return "ParsedArgs [args=" + args + ", varargs=" + varargs + ", options=" + options
136-
+ ", flags=" + flags + "]";
125+
return "ParsedArgs [args=" + args + ", options=" + options + ", flags=" + flags + "]";
137126
}
138127
}
139128

@@ -187,8 +176,6 @@ public int getIndex() {
187176
* positional arguments, even if they look like switches.
188177
*
189178
* @param args the command line arguments to parse, generally from the main function
190-
* @param maxArgs the maximum number of positional arguments, after which positional arguments
191-
* should be collected in varargs in the result
192179
* @param shortOptionNames a map from valid short option names to the string to use to collect
193180
* values into the options result. Keys in shortOptionNames must not appear in any other
194181
* option or flag name map in character form. If a short option name does not appear in
@@ -220,19 +207,19 @@ public int getIndex() {
220207
* @return the parsed arguments
221208
*
222209
* @throws NullPointerException if any argument is null
223-
* @throws IllegalArgumentException if maxArgs is negative; or if any short option or flag name
224-
* appears in more than one of shortOptionNames, shortPositiveFlagNames, and
225-
* shortNegativeFlagNames; or if any long option or flag name appears in more than one of
226-
* longOptionNames, longPositiveFlagNames, and longNegativeFlagNames.
210+
* @throws IllegalArgumentException if any short option or flag name appears in more than one of
211+
* shortOptionNames, shortPositiveFlagNames, and shortNegativeFlagNames; or if any long
212+
* option or flag name appears in more than one of longOptionNames, longPositiveFlagNames,
213+
* and longNegativeFlagNames.
227214
* @throws IllegalSyntaxException if any short switch is not an element in shortOptionNames,
228215
* shortPositiveFlagNames, or shortNegativeFlagNames; or if any long switch is not an
229216
* element in longOptionNames, longPositiveFlagNames, or longNegativeFlagNames; or if any
230217
* option switch does not have a value; or if any flag switch has a value.
231218
*/
232-
public static ParsedArgs parseArgs(List<String> args, int maxArgs,
233-
Map<Character, String> shortOptionNames, Map<String, String> longOptionNames,
234-
Map<Character, String> shortPositiveFlagNames, Map<String, String> longPositiveFlagNames,
235-
Map<Character, String> shortNegativeFlagNames, Map<String, String> longNegativeFlagNames) {
219+
public static ParsedArgs parseArgs(List<String> args, Map<Character, String> shortOptionNames,
220+
Map<String, String> longOptionNames, Map<Character, String> shortPositiveFlagNames,
221+
Map<String, String> longPositiveFlagNames, Map<Character, String> shortNegativeFlagNames,
222+
Map<String, String> longNegativeFlagNames) {
236223
if (args == null)
237224
throw new NullPointerException();
238225
if (shortOptionNames == null)
@@ -247,8 +234,6 @@ public static ParsedArgs parseArgs(List<String> args, int maxArgs,
247234
throw new NullPointerException();
248235
if (longNegativeFlagNames == null)
249236
throw new NullPointerException();
250-
if (maxArgs < 0)
251-
throw new IllegalArgumentException("maxArgs must be non-negative");
252237

253238
final Set<Character> duplicateShortKeys = duplicates(shortOptionNames.keySet(),
254239
shortPositiveFlagNames.keySet(), shortNegativeFlagNames.keySet());
@@ -262,7 +247,6 @@ public static ParsedArgs parseArgs(List<String> args, int maxArgs,
262247

263248
// Prepare result holders
264249
List<String> positionalArgs = new ArrayList<>();
265-
List<String> varargs = new ArrayList<>();
266250
Map<String, List<String>> options = new LinkedHashMap<>();
267251
Map<String, List<Boolean>> flags = new LinkedHashMap<>();
268252

@@ -278,24 +262,20 @@ public static ParsedArgs parseArgs(List<String> args, int maxArgs,
278262
// flags.computeIfAbsent(flagName, k -> new ArrayList<>()).add(boolVal);
279263
// };
280264

281-
boolean positionalOnly = false;
265+
boolean separated = false;
282266
final ListIterator<String> iterator = args.listIterator();
283267
while (iterator.hasNext()) {
284268
final String arg = iterator.next();
285269

286270
// If the argument is exactly `--`, all subsequent are positional
287-
if ("--".equals(arg) && positionalOnly == false) {
288-
positionalOnly = true;
271+
if ("--".equals(arg) && separated == false) {
272+
separated = true;
289273
continue;
290274
}
291275

292276
// If we've already encountered `--`, everything is a positional arg
293-
if (positionalOnly) {
294-
if (positionalArgs.size() < maxArgs) {
295-
positionalArgs.add(arg);
296-
} else {
297-
varargs.add(arg);
298-
}
277+
if (separated) {
278+
positionalArgs.add(arg);
299279
continue;
300280
}
301281

@@ -407,15 +387,11 @@ public static ParsedArgs parseArgs(List<String> args, int maxArgs,
407387
}
408388
} else {
409389
// POSITIONAL ARG
410-
if (positionalArgs.size() < maxArgs) {
411-
positionalArgs.add(arg);
412-
} else {
413-
varargs.add(arg);
414-
}
390+
positionalArgs.add(arg);
415391
}
416392
}
417393

418-
return new ParsedArgs(positionalArgs, varargs, options, flags);
394+
return new ParsedArgs(positionalArgs, options, flags);
419395
}
420396

421397
@SafeVarargs

0 commit comments

Comments
 (0)