Skip to content

Commit a899963

Browse files
dchenzremkop
authored andcommitted
[#2516] Hide user prompt if stdin is not connected to a terminal
- Moved prompt into parent method and check if there's a tty. - Removed the builtin prompt provided by Console.readPassword. - Fixed existing interactive tests that rely on tty=true. - Added test cases for tty=false asserting stdout is empty. Fixes #2516
1 parent 10df5ba commit a899963

2 files changed

Lines changed: 92 additions & 10 deletions

File tree

src/main/java/picocli/CommandLine.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14993,14 +14993,19 @@ private boolean assertNoMissingParameters(ArgSpec argSpec, Range arity, Stack<St
1499314993

1499414994
char[] readUserInput(ArgSpec argSpec) {
1499514995
String name = argSpec.isOption() ? ((OptionSpec) argSpec).longestName() : "position " + position;
14996-
String desc = str(argSpec.description(), 0);
14997-
String standardPrompt = empty(desc) ? String.format("Enter value for %s: ", name) : String.format("Enter value for %s (%s): ", name, desc);
14998-
String prompt = empty(argSpec.prompt()) ? standardPrompt : argSpec.prompt();
14996+
14997+
if (Help.Ansi.isTTY()) {
14998+
String desc = str(argSpec.description(), 0);
14999+
String standardPrompt = empty(desc) ? String.format("Enter value for %s: ", name) : String.format("Enter value for %s (%s): ", name, desc);
15000+
String prompt = empty(argSpec.prompt()) ? standardPrompt : argSpec.prompt();
15001+
System.out.print(prompt);
15002+
}
15003+
1499915004
try {
1500015005
Tracer t = tracer();
1500115006
if (t.isDebug()) {
1500215007
t.debug("Reading value for %s from console...", name);}
15003-
char[] result = argSpec.echo() ? readUserInputWithEchoing(prompt) : readPassword(prompt);
15008+
char[] result = argSpec.echo() ? readUserInputWithEchoing() : readPassword();
1500415009
if (t.isDebug()) {
1500515010
t.debug(createUserInputDebugString(argSpec, result, name));}
1500615011
return result;
@@ -15013,17 +15018,16 @@ private String createUserInputDebugString(ArgSpec argSpec, char[] result, String
1501315018
String.format("User entered %s for %s.%n", new String(result), name) :
1501415019
String.format("User entered %d characters for %s.%n", result.length, name);
1501515020
}
15016-
char[] readPassword(String prompt) {
15021+
char[] readPassword() {
1501715022
try {
1501815023
Object console = System.class.getDeclaredMethod("console").invoke(null);
15019-
Method method = Class.forName("java.io.Console").getDeclaredMethod("readPassword", String.class, Object[].class);
15020-
return (char[]) method.invoke(console, prompt, new Object[0]);
15024+
Method method = Class.forName("java.io.Console").getDeclaredMethod("readPassword");
15025+
return (char[]) method.invoke(console);
1502115026
} catch (Exception e) {
15022-
return readUserInputWithEchoing(prompt);
15027+
return readUserInputWithEchoing();
1502315028
}
1502415029
}
15025-
char[] readUserInputWithEchoing(String prompt) {
15026-
System.out.print(prompt);
15030+
char[] readUserInputWithEchoing() {
1502715031
InputStreamReader isr = new InputStreamReader(System.in);
1502815032
BufferedReader in = new BufferedReader(isr);
1502915033
try {

src/test/java/picocli/InteractiveArgTest.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import org.junit.contrib.java.lang.system.ProvideSystemProperty;
77
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
88
import org.junit.rules.TestRule;
9+
10+
import picocli.CommandLine.Help;
911
import picocli.CommandLine.Model.ArgSpec;
1012
import picocli.CommandLine.Option;
1113
import picocli.CommandLine.Parameters;
@@ -37,11 +39,13 @@ static class Streams {
3739
PrintStream out = System.out;
3840
PrintStream err = System.err;
3941
InputStream in = System.in;
42+
Boolean isTTY = Help.Ansi.tty;
4043

4144
void reset() {
4245
System.setOut(out);
4346
System.setOut(err);
4447
System.setIn(in);
48+
Help.Ansi.tty = isTTY;
4549
}
4650
}
4751

@@ -54,8 +58,13 @@ static class Capture {
5458
}
5559

5660
Capture(String input) {
61+
this(input, true);
62+
}
63+
64+
Capture(String input, boolean isTTY) {
5765
System.setOut(new PrintStream(baos));
5866
System.setErr(new PrintStream(errBaos));
67+
Help.Ansi.tty = isTTY;
5968
if (input != null) {
6069
System.setIn(new ByteArrayInputStream(input.getBytes()));
6170
}
@@ -131,6 +140,44 @@ class App {
131140
}
132141
}
133142

143+
@Test
144+
public void testInteractiveOptionReadsFromStdInNoTTY() {
145+
org.junit.Assume.assumeTrue(System.getProperty("java.version").compareTo("22") < 0);
146+
class App {
147+
@Option(names = "-x", description = {"Pwd", "line2"}, interactive = true) int x;
148+
@Option(names = "-z") int z;
149+
}
150+
151+
Streams streams = new Streams();
152+
System.setProperty("picocli.trace", "DEBUG");
153+
try {
154+
Capture capture = new Capture("1234567890", false);
155+
156+
App app = new App();
157+
CommandLine cmd = new CommandLine(app);
158+
ParseResult result = cmd.parseArgs("-x");
159+
ArgSpec specX = result.matchedArgs().get(0);
160+
assertThat(specX.toString(), containsString("App.x"));
161+
162+
assertEquals("", capture.out());
163+
assertEquals(1234567890, app.x);
164+
assertEquals(0, app.z);
165+
166+
String trace = capture.err();
167+
assertThat(trace, containsString("User entered 10 characters"));
168+
assertThat(trace, containsString(
169+
"Setting " + specX.toString() + " to *****(masked) (interactive value)"));
170+
assertThat(trace, not(containsString("1234567890")));
171+
172+
cmd.parseArgs("-z", "678");
173+
174+
assertEquals(0, app.x);
175+
assertEquals(678, app.z);
176+
} finally {
177+
streams.reset();
178+
}
179+
}
180+
134181
@Test
135182
public void testInteractiveOptionReadsFromStdInWithEchoing() {
136183
class App {
@@ -162,6 +209,37 @@ class App {
162209
}
163210
}
164211

212+
@Test
213+
public void testInteractiveOptionReadsFromStdInWithEchoingNoTTY() {
214+
class App {
215+
@Option(names = "-x", description = {"Pwd", "line2"}, interactive = true, echo = true) int x;
216+
}
217+
218+
Streams streams = new Streams();
219+
System.setProperty("picocli.trace", "DEBUG");
220+
try {
221+
Capture capture = new Capture("1234567890", false);
222+
223+
App app = new App();
224+
CommandLine cmd = new CommandLine(app);
225+
ParseResult result = cmd.parseArgs("-x");
226+
ArgSpec specX = result.matchedArgs().get(0);
227+
assertThat(specX.toString(), containsString("App.x"));
228+
229+
assertEquals("", capture.out());
230+
assertEquals(1234567890, app.x);
231+
232+
String trace = capture.err();
233+
assertThat(trace, containsString("User entered 1234567890"));
234+
assertThat(trace, containsString(
235+
"Setting " + specX.toString() + " to 1234567890"));
236+
assertThat(trace, not(containsString("10 characters")));
237+
assertThat(trace, not(containsString("***")));
238+
} finally {
239+
streams.reset();
240+
}
241+
}
242+
165243
@Test
166244
public void testInteractiveOptionWithoutDescriptionStandardPrompt() {
167245
org.junit.Assume.assumeTrue(System.getProperty("java.version").compareTo("22") < 0);

0 commit comments

Comments
 (0)