2525import java .util .HashSet ;
2626import java .util .List ;
2727import java .util .Map ;
28+ import java .util .Optional ;
2829import java .util .Set ;
2930import javax .annotation .Nullable ;
3031
@@ -192,10 +193,82 @@ public CommandContext(String[] args, @Nullable Set<Character> valueFlagNames, bo
192193 return suggestionContext ;
193194 }
194195
196+ /**
197+ * Is the command being executed, rather than completed?
198+ */
199+ public boolean isExecuting () {
200+ return getSuggestionContext () == null ;
201+ }
202+
203+ /**
204+ * Is the command being completed, rather than executed?
205+ */
206+ public boolean isSuggesting () {
207+ return getSuggestionContext () != null ;
208+ }
209+
210+ /**
211+ * Is the given argument being completed?
212+ */
213+ public boolean isSuggestingArgument (int index ) {
214+ return isSuggesting () && getSuggestionContext ().isArgument (index );
215+ }
216+
217+ /**
218+ * Is the given flag being completed?
219+ */
220+ public boolean isSuggestingFlag (char flag ) {
221+ return isSuggesting () && getSuggestionContext ().isFlag (flag );
222+ }
223+
224+ /**
225+ * If the given argument is being completed, generate suggestions based on the given choices.
226+ */
227+ public void suggestArgument (int index , Iterable <String > choices ) throws SuggestException {
228+ if (isSuggesting () && getSuggestionContext ().isArgument ()) {
229+ getSuggestionContext ().suggestArgument (index , choices );
230+ }
231+ }
232+
233+ /**
234+ * If the given flag is being completed, generate suggestions based on the given choices.
235+ */
236+ public void suggestFlag (char flag , Iterable <String > choices ) throws SuggestException {
237+ if (isSuggesting ()) {
238+ getSuggestionContext ().suggestFlag (flag , choices );
239+ }
240+ }
241+
242+ /**
243+ * If the command is being completed from anywhere at or after the given argument index,
244+ * generate suggestions for the entire command from that index, based on the given choices.
245+ */
246+ public void suggestJoinedArguments (int start , Iterable <String > choices ) throws SuggestException {
247+ final SuggestionContext ctx = getSuggestionContext ();
248+ if (ctx != null && ctx .isArgument ()) {
249+ if (start == ctx .getIndex ()) {
250+ ctx .suggestArgument (start , choices );
251+ } else if (start < ctx .getIndex ()) {
252+ final String prefix = String .join (" " , parsedArgs .subList (start , ctx .getIndex ())).toLowerCase () + " " ;
253+ final List <String > filtered = new ArrayList <>();
254+ for (String choice : choices ) {
255+ if (choice .toLowerCase ().startsWith (prefix )) {
256+ filtered .add (choice .substring (prefix .length ()));
257+ }
258+ }
259+ ctx .suggestArgument (ctx .getIndex (), filtered );
260+ }
261+ }
262+ }
263+
195264 public String getCommand () {
196265 return command ;
197266 }
198267
268+ public String [] getOriginalArgs () {
269+ return originalArgs ;
270+ }
271+
199272 public boolean matches (String command ) {
200273 return this .command .equalsIgnoreCase (command );
201274 }
@@ -204,10 +277,46 @@ public String getString(int index) {
204277 return parsedArgs .get (index );
205278 }
206279
280+ /**
281+ * Return the argument at the given index as a String, if it is present.
282+ */
283+ public Optional <String > tryString (int index ) {
284+ return index < parsedArgs .size () ? Optional .of (parsedArgs .get (index ))
285+ : Optional .empty ();
286+ }
287+
207288 public String getString (int index , String def ) {
208289 return index < parsedArgs .size () ? parsedArgs .get (index ) : def ;
209290 }
210291
292+ /**
293+ * Return the argument at the given index as a String.
294+ * @throws CommandException if the argument is missing
295+ */
296+ public String string (int index ) throws CommandException {
297+ if (index >= parsedArgs .size ()) {
298+ throw new CommandUsageException ("Missing argument" );
299+ }
300+ return getString (index );
301+ }
302+
303+ /**
304+ * Return the argument at the given index as a String, or generate suggestions
305+ * if that argument is being completed.
306+ *
307+ * @throws CommandException if the argument is missing
308+ * @throws SuggestException if the argument is being completed
309+ */
310+ public String string (int index , Iterable <String > choices ) throws CommandException , SuggestException {
311+ suggestArgument (index , choices );
312+ return string (index );
313+ }
314+
315+ public Optional <String > tryString (int index , Iterable <String > choices ) throws SuggestException {
316+ suggestArgument (index , choices );
317+ return tryString (index );
318+ }
319+
211320 public String getJoinedStrings (int initialIndex ) {
212321 initialIndex = originalArgIndices .get (initialIndex );
213322 StringBuilder buffer = new StringBuilder (originalArgs [initialIndex ]);
@@ -216,11 +325,70 @@ public String getJoinedStrings(int initialIndex) {
216325 }
217326 return buffer .toString ();
218327 }
219-
328+
329+ public String getJoinedStrings (int initialIndex , String def ) {
330+ return initialIndex < originalArgIndices .size () ? getJoinedStrings (initialIndex ) : def ;
331+ }
332+
333+ /**
334+ * Return the rest of the command line, starting at the given argument index.
335+ *
336+ * Any flags that appear after the given index are included in the result.
337+ *
338+ * @throws CommandException if the argument is missing
339+ */
340+ public String joinedStrings (int initialIndex ) throws CommandException {
341+ if (initialIndex >= originalArgIndices .size ()) {
342+ throw new CommandUsageException ("Missing argument" );
343+ }
344+ return getJoinedStrings (initialIndex );
345+ }
346+
347+ public String joinedStrings (int initialIndex , Iterable <String > choices ) throws CommandException , SuggestException {
348+ suggestJoinedArguments (initialIndex , choices );
349+ return joinedStrings (initialIndex );
350+ }
351+
352+ public Optional <String > tryJoinedStrings (int initialIndex ) {
353+ return initialIndex < originalArgIndices .size () ? Optional .of (getJoinedStrings (initialIndex ))
354+ : Optional .empty ();
355+ }
356+
357+ public Optional <String > tryJoinedStrings (int initialIndex , Iterable <String > choices ) throws SuggestException {
358+ suggestJoinedArguments (initialIndex , choices );
359+ return tryJoinedStrings (initialIndex );
360+ }
361+
220362 public String getRemainingString (int start ) {
221363 return getString (start , parsedArgs .size () - 1 );
222364 }
223365
366+ /**
367+ * Return the given argument and all arguments after it, joined with spaces.
368+ *
369+ * Flags are never included in the result, even if they appear between between
370+ * the arguments that are included.
371+ *
372+ * @throws CommandException if the argument is missing
373+ */
374+ public String remainingString (int start ) throws CommandException {
375+ return string (start , parsedArgs .size () - 1 );
376+ }
377+
378+ public String remainingString (int start , Iterable <String > choices ) throws CommandException , SuggestException {
379+ suggestJoinedArguments (start , choices );
380+ return remainingString (start );
381+ }
382+
383+ public Optional <String > tryRemainingString (int start ) {
384+ return tryString (start , parsedArgs .size () - 1 );
385+ }
386+
387+ public Optional <String > tryRemainingString (int start , Iterable <String > choices ) throws SuggestException {
388+ suggestJoinedArguments (start , choices );
389+ return tryRemainingString (start );
390+ }
391+
224392 public String getString (int start , int end ) {
225393 StringBuilder buffer = new StringBuilder (parsedArgs .get (start ));
226394 for (int i = start + 1 ; i < end + 1 ; ++i ) {
@@ -229,6 +397,18 @@ public String getString(int start, int end) {
229397 return buffer .toString ();
230398 }
231399
400+ public String string (int start , int end ) throws CommandException {
401+ if (start >= parsedArgs .size () || end >= parsedArgs .size ()) {
402+ throw new CommandUsageException ("Missing argument" );
403+ }
404+ return getString (start , end );
405+ }
406+
407+ public Optional <String > tryString (int start , int end ) {
408+ return start < parsedArgs .size () && end < parsedArgs .size () ? Optional .of (getString (start , end ))
409+ : Optional .empty ();
410+ }
411+
232412 public int getInteger (int index ) throws CommandNumberFormatException {
233413 final String text = parsedArgs .get (index );
234414 try {
@@ -291,7 +471,7 @@ public Map<Character, String> getValueFlags() {
291471 return valueFlags ;
292472 }
293473
294- public String getFlag (char ch ) {
474+ public @ Nullable String getFlag (char ch ) {
295475 return valueFlags .get (ch );
296476 }
297477
@@ -304,6 +484,20 @@ public String getFlag(char ch, String def) {
304484 return value ;
305485 }
306486
487+ public @ Nullable String flagOrNull (char ch , Iterable <String > choices ) throws SuggestException {
488+ suggestFlag (ch , choices );
489+ return getFlag (ch );
490+ }
491+
492+ public Optional <String > tryFlag (char ch ) {
493+ return Optional .ofNullable (getFlag (ch ));
494+ }
495+
496+ public Optional <String > tryFlag (char ch , Iterable <String > choices ) throws SuggestException {
497+ suggestFlag (ch , choices );
498+ return tryFlag (ch );
499+ }
500+
307501 public int getFlagInteger (char ch ) throws CommandNumberFormatException {
308502 final String text = valueFlags .get (ch );
309503 try {
0 commit comments