26
26
27
27
import jakarta .validation .Validator ;
28
28
import jakarta .validation .ValidatorFactory ;
29
-
30
29
import org .jline .terminal .Terminal ;
31
30
import org .jline .utils .Signals ;
32
31
import org .slf4j .Logger ;
39
38
import org .springframework .shell .command .CommandAlias ;
40
39
import org .springframework .shell .command .CommandCatalog ;
41
40
import org .springframework .shell .command .CommandExecution ;
42
- import org .springframework .shell .command .CommandOption ;
43
41
import org .springframework .shell .command .CommandExecution .CommandExecutionException ;
44
42
import org .springframework .shell .command .CommandExecution .CommandExecutionHandlerMethodArgumentResolvers ;
43
+ import org .springframework .shell .command .CommandOption ;
45
44
import org .springframework .shell .command .CommandRegistration ;
45
+ import org .springframework .shell .command .CommandExceptionResolver ;
46
+ import org .springframework .shell .command .CommandHandlingResult ;
46
47
import org .springframework .shell .completion .CompletionResolver ;
47
48
import org .springframework .shell .context .InteractionMode ;
48
49
import org .springframework .shell .context .ShellContext ;
50
+ import org .springframework .shell .exit .ExitCodeExceptionProvider ;
49
51
import org .springframework .shell .exit .ExitCodeMappings ;
50
52
import org .springframework .util .StringUtils ;
51
53
@@ -73,6 +75,8 @@ public class Shell {
73
75
private ConversionService conversionService = new DefaultConversionService ();
74
76
private final ShellContext shellContext ;
75
77
private final ExitCodeMappings exitCodeMappings ;
78
+ private Exception handlingResultNonInt = null ;
79
+ private CommandHandlingResult processExceptionNonInt = null ;
76
80
77
81
/**
78
82
* Marker object to distinguish unresolved arguments from {@code null}, which is a valid
@@ -81,6 +85,7 @@ public class Shell {
81
85
protected static final Object UNRESOLVED = new Object ();
82
86
83
87
private Validator validator = Utils .defaultValidator ();
88
+ private List <CommandExceptionResolver > exceptionResolvers = new ArrayList <>();
84
89
85
90
public Shell (ResultHandlerService resultHandlerService , CommandCatalog commandRegistry , Terminal terminal ,
86
91
ShellContext shellContext , ExitCodeMappings exitCodeMappings ) {
@@ -111,6 +116,18 @@ public void setValidatorFactory(ValidatorFactory validatorFactory) {
111
116
this .validator = validatorFactory .getValidator ();
112
117
}
113
118
119
+ @ Autowired (required = false )
120
+ public void setExceptionResolvers (List <CommandExceptionResolver > exceptionResolvers ) {
121
+ this .exceptionResolvers = exceptionResolvers ;
122
+ }
123
+
124
+ private ExitCodeExceptionProvider exitCodeExceptionProvider ;
125
+
126
+ @ Autowired (required = false )
127
+ public void setExitCodeExceptionProvider (ExitCodeExceptionProvider exitCodeExceptionProvider ) {
128
+ this .exitCodeExceptionProvider = exitCodeExceptionProvider ;
129
+ }
130
+
114
131
/**
115
132
* The main program loop: acquire input, try to match it to a command and evaluate. Repeat
116
133
* until a {@link ResultHandler} causes the process to exit or there is no input.
@@ -152,6 +169,13 @@ public void run(InputProvider inputProvider) throws Exception {
152
169
else if (result instanceof Exception ) {
153
170
throw (Exception ) result ;
154
171
}
172
+ if (handlingResultNonInt instanceof CommandExecution .CommandParserExceptionsException ) {
173
+ throw (CommandExecution .CommandParserExceptionsException ) handlingResultNonInt ;
174
+ }
175
+ else if (processExceptionNonInt != null && processExceptionNonInt .exitCode () != null
176
+ && exitCodeExceptionProvider != null ) {
177
+ throw exitCodeExceptionProvider .apply (null , processExceptionNonInt .exitCode ());
178
+ }
155
179
}
156
180
}
157
181
}
@@ -165,74 +189,124 @@ else if (result instanceof Exception) {
165
189
* result
166
190
* </p>
167
191
*/
168
- public Object evaluate (Input input ) {
192
+ private Object evaluate (Input input ) {
169
193
if (noInput (input )) {
170
194
return NO_INPUT ;
171
195
}
172
196
173
- String line = input .words ().stream ().collect (Collectors .joining (" " )).trim ();
197
+ List <String > words = input .words ();
198
+ String line = words .stream ().collect (Collectors .joining (" " )).trim ();
174
199
String command = findLongestCommand (line );
175
200
176
- List <String > words = input .words ();
201
+ if (command == null ) {
202
+ return new CommandNotFound (words );
203
+ }
204
+
177
205
log .debug ("Evaluate input with line=[{}], command=[{}]" , line , command );
178
- if (command != null ) {
179
206
180
- Optional <CommandRegistration > commandRegistration = commandRegistry .getRegistrations ().values ().stream ()
181
- .filter (r -> {
182
- if (r .getCommand ().equals (command )) {
207
+ Optional <CommandRegistration > commandRegistration = commandRegistry .getRegistrations ().values ().stream ()
208
+ .filter (r -> {
209
+ if (r .getCommand ().equals (command )) {
210
+ return true ;
211
+ }
212
+ for (CommandAlias a : r .getAliases ()) {
213
+ if (a .getCommand ().equals (command )) {
183
214
return true ;
184
215
}
185
- for (CommandAlias a : r .getAliases ()) {
186
- if (a .getCommand ().equals (command )) {
187
- return true ;
188
- }
189
- }
190
- return false ;
191
- })
192
- .findFirst ();
193
-
194
- if (commandRegistration .isPresent ()) {
195
- if (this .exitCodeMappings != null ) {
196
- List <Function <Throwable , Integer >> mappingFunctions = commandRegistration .get ().getExitCode ()
197
- .getMappingFunctions ();
198
- this .exitCodeMappings .reset (mappingFunctions );
199
216
}
217
+ return false ;
218
+ })
219
+ .findFirst ();
200
220
201
- List <String > wordsForArgs = wordsForArguments (command , words );
221
+ if (commandRegistration .isEmpty ()) {
222
+ return new CommandNotFound (words );
223
+ }
202
224
203
- Thread commandThread = Thread .currentThread ();
204
- Object sh = Signals .register ("INT" , () -> commandThread .interrupt ());
205
- try {
206
- CommandExecution execution = CommandExecution
207
- .of (argumentResolvers != null ? argumentResolvers .getResolvers () : null , validator , terminal , conversionService );
208
- return execution .evaluate (commandRegistration .get (), wordsForArgs .toArray (new String [0 ]));
209
- }
210
- catch (UndeclaredThrowableException e ) {
211
- if (e .getCause () instanceof InterruptedException || e .getCause () instanceof ClosedByInterruptException ) {
212
- Thread .interrupted (); // to reset interrupted flag
213
- }
214
- return e .getCause ();
215
- }
216
- catch (CommandExecutionException e ) {
217
- return e .getCause ();
218
- }
219
- catch (Exception e ) {
220
- return e ;
225
+ if (this .exitCodeMappings != null ) {
226
+ List <Function <Throwable , Integer >> mappingFunctions = commandRegistration .get ().getExitCode ()
227
+ .getMappingFunctions ();
228
+ this .exitCodeMappings .reset (mappingFunctions );
229
+ }
230
+
231
+ List <String > wordsForArgs = wordsForArguments (command , words );
232
+
233
+ Thread commandThread = Thread .currentThread ();
234
+ Object sh = Signals .register ("INT" , () -> commandThread .interrupt ());
235
+
236
+ CommandExecution execution = CommandExecution .of (
237
+ argumentResolvers != null ? argumentResolvers .getResolvers () : null , validator , terminal ,
238
+ conversionService );
239
+
240
+ List <CommandExceptionResolver > commandExceptionResolvers = commandRegistration .get ().getExceptionResolvers ();
241
+
242
+ Object evaluate = null ;
243
+ Exception e = null ;
244
+ try {
245
+ evaluate = execution .evaluate (commandRegistration .get (), wordsForArgs .toArray (new String [0 ]));
246
+ }
247
+ catch (UndeclaredThrowableException ute ) {
248
+ if (ute .getCause () instanceof InterruptedException || ute .getCause () instanceof ClosedByInterruptException ) {
249
+ Thread .interrupted (); // to reset interrupted flag
250
+ }
251
+ return ute .getCause ();
252
+ }
253
+ catch (CommandExecutionException e1 ) {
254
+ return e1 .getCause ();
255
+ }
256
+ catch (Exception e2 ) {
257
+ e = e2 ;
258
+ }
259
+ finally {
260
+ Signals .unregister ("INT" , sh );
261
+ }
262
+ if (e != null ) {
263
+ try {
264
+ CommandHandlingResult processException = processException (commandExceptionResolvers , e );
265
+ processExceptionNonInt = processException ;
266
+ if (processException != null ) {
267
+ handlingResultNonInt = e ;
268
+ this .terminal .writer ().append (processException .message ());
269
+ this .terminal .writer ().flush ();
270
+ return null ;
221
271
}
222
- finally {
223
- Signals .unregister ("INT" , sh );
272
+ } catch (Exception e1 ) {
273
+ e = e1 ;
274
+ }
275
+ }
276
+ if (e != null ) {
277
+ evaluate = e ;
278
+ }
279
+ return evaluate ;
280
+ }
281
+
282
+ private CommandHandlingResult processException (List <CommandExceptionResolver > commandExceptionResolvers , Exception e )
283
+ throws Exception {
284
+ CommandHandlingResult r = null ;
285
+ for (CommandExceptionResolver resolver : commandExceptionResolvers ) {
286
+ r = resolver .resolve (e );
287
+ if (r != null ) {
288
+ break ;
289
+ }
290
+ }
291
+ if (r == null ) {
292
+ for (CommandExceptionResolver resolver : exceptionResolvers ) {
293
+ r = resolver .resolve (e );
294
+ if (r != null ) {
295
+ break ;
224
296
}
225
297
}
298
+ }
299
+ if (r != null ) {
300
+ if (r .isEmpty ()) {
301
+ return null ;
302
+ }
226
303
else {
227
- return new CommandNotFound ( words ) ;
304
+ return r ;
228
305
}
229
306
}
230
- else {
231
- return new CommandNotFound (words );
232
- }
307
+ throw e ;
233
308
}
234
309
235
-
236
310
/**
237
311
* Return true if the parsed input ends up being empty (<em>e.g.</em> hitting ENTER on an
238
312
* empty line or blank space).
0 commit comments