• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 11909967740

19 Nov 2024 09:23AM UTC coverage: 67.154% (-0.1%) from 67.282%
11909967740

Pull #748

github

web-flow
Merge 0bd754cdb into 2966a61ee
Pull Request #748: #737: Added cd command to shell commandlet

2461 of 4009 branches covered (61.39%)

Branch coverage included in aggregate %.

6400 of 9186 relevant lines covered (69.67%)

3.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

3.13
cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java
1
package com.devonfw.tools.ide.commandlet;
2

3
import java.io.IOException;
4
import java.nio.file.Files;
5
import java.nio.file.Path;
6
import java.nio.file.Paths;
7
import java.util.Iterator;
8

9
import org.fusesource.jansi.AnsiConsole;
10
import org.jline.reader.Completer;
11
import org.jline.reader.EndOfFileException;
12
import org.jline.reader.LineReader;
13
import org.jline.reader.LineReaderBuilder;
14
import org.jline.reader.MaskingCallback;
15
import org.jline.reader.Parser;
16
import org.jline.reader.UserInterruptException;
17
import org.jline.reader.impl.DefaultParser;
18
import org.jline.reader.impl.completer.AggregateCompleter;
19
import org.jline.reader.impl.completer.StringsCompleter;
20
import org.jline.terminal.Terminal;
21
import org.jline.terminal.TerminalBuilder;
22
import org.jline.widget.AutosuggestionWidgets;
23

24
import com.devonfw.tools.ide.cli.CliArgument;
25
import com.devonfw.tools.ide.cli.CliArguments;
26
import com.devonfw.tools.ide.completion.IdeCompleter;
27
import com.devonfw.tools.ide.context.AbstractIdeContext;
28
import com.devonfw.tools.ide.context.IdeContext;
29
import com.devonfw.tools.ide.property.BooleanProperty;
30
import com.devonfw.tools.ide.property.KeywordProperty;
31
import com.devonfw.tools.ide.property.Property;
32

33
/**
34
 * {@link Commandlet} for internal interactive shell with build-in auto-completion and help.
35
 */
36
public final class ShellCommandlet extends Commandlet {
37

38
  private static final int AUTOCOMPLETER_MAX_RESULTS = 50;
39

40
  private static final int RC_EXIT = 987654321;
41

42
  /**
43
   * The constructor.
44
   *
45
   * @param context the {@link IdeContext}.
46
   */
47
  public ShellCommandlet(IdeContext context) {
48

49
    super(context);
3✔
50
    addKeyword(getName());
4✔
51
  }
1✔
52

53
  @Override
54
  public String getName() {
55

56
    return "shell";
2✔
57
  }
58

59
  @Override
60
  public boolean isIdeHomeRequired() {
61

62
    return false;
2✔
63
  }
64

65
  @Override
66
  public void run() {
67

68
    try {
69
      Parser parser = new DefaultParser();
×
70
      try (Terminal terminal = TerminalBuilder.builder().build()) {
×
71
        // initialize our own completer here and add exit as an autocompletion option
72
        Completer completer = new AggregateCompleter(
×
73
            new StringsCompleter("exit"), new IdeCompleter((AbstractIdeContext) this.context));
74

75
        LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(completer).parser(parser)
×
76
            .variable(LineReader.LIST_MAX, AUTOCOMPLETER_MAX_RESULTS).build();
×
77

78
        // Create autosuggestion widgets
79
        AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader);
×
80
        // Enable autosuggestions
81
        autosuggestionWidgets.enable();
×
82

83
        // TODO: implement TailTipWidgets, see: https://github.com/devonfw/IDEasy/issues/169
84

85
        String prompt = "ide> ";
×
86
        String rightPrompt = null;
×
87
        String line;
88

89
        AnsiConsole.systemInstall();
×
90
        while (true) {
91
          try {
92
            line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null);
×
93
            line = line.trim();
×
94
            if (line.equals("exit")) {
×
95
              return;
×
96
            }
97
            reader.getHistory().add(line);
×
98
            int rc = runCommand(line);
×
99
            if (rc == RC_EXIT) {
×
100
              return;
×
101
            }
102
          } catch (UserInterruptException e) {
×
103
            // Ignore CTRL+C
104
            return;
×
105
          } catch (EndOfFileException e) {
×
106
            // CTRL+D
107
            return;
×
108
          } finally {
109
            AnsiConsole.systemUninstall();
×
110
          }
×
111
        }
112

113
      } catch (IOException e) {
×
114
        throw new RuntimeException(e);
×
115
      }
116
    } catch (Exception e) {
×
117
      throw new RuntimeException("Unexpected error during interactive auto-completion", e);
×
118
    }
119
  }
120

121
  /**
122
   * Converts String of arguments to array and runs the command
123
   *
124
   * @param args String of arguments
125
   * @return status code
126
   */
127
  private int runCommand(String args) {
128

129
    if ("exit".equals(args) || "quit".equals(args)) {
×
130
      return RC_EXIT;
×
131
    }
132
    String[] arguments = args.split(" ", 0);
×
133
    CliArguments cliArgs = new CliArguments(arguments);
×
134
    cliArgs.next();
×
135

136
    if ("cd".equals(arguments[0])) {
×
137
      return changeDirectory(cliArgs);
×
138
    }
139

140
    return ((AbstractIdeContext) this.context).run(cliArgs);
×
141
  }
142

143
  private int changeDirectory(CliArguments cliArgs) {
144
    if (!cliArgs.hasNext()) {
×
145
      this.context.error("Error: 'cd' requires a directory argument.");
×
146
      return -1;
×
147
    }
148

149
    String targetDir = String.valueOf(cliArgs.next());
×
150
    Path path = Paths.get(targetDir);
×
151

152
    // If the given path is relative, resolve it relative to the current directory
153
    if (!path.isAbsolute()) {
×
154
      path = context.getCwd();
×
155
    }
156

157
    // Check if the path exists and is a directory
158
    if (Files.exists(path) && Files.isDirectory(path)) {
×
159
      // Set the current working directory to the new path
160
      context.setCwd(path, context.getWorkspaceName(), context.getIdeHome());
×
161
      this.context.info("Changed directory to: " + path.toAbsolutePath());
×
162
      return 0;
×
163
    } else {
164
      this.context.error("Error: Directory not found: " + targetDir);
×
165
      return -1;
×
166
    }
167
  }
168
  
169
  /**
170
   * @param argument the current {@link CliArgument} (position) to match.
171
   * @param commandlet the potential {@link Commandlet} to match.
172
   * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have been applied (set in the {@link Commandlet}
173
   *     and {@link Commandlet#validate() validated}), {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
174
   */
175
  private boolean apply(CliArgument argument, Commandlet commandlet) {
176

177
    this.context.trace("Trying to match arguments to commandlet {}", commandlet.getName());
×
178
    CliArgument currentArgument = argument;
×
179
    Iterator<Property<?>> valueIterator = commandlet.getValues().iterator();
×
180
    Property<?> currentProperty = null;
×
181
    boolean endOpts = false;
×
182
    while (!currentArgument.isEnd()) {
×
183
      if (currentArgument.isEndOptions()) {
×
184
        endOpts = true;
×
185
      } else {
186
        String arg = currentArgument.get();
×
187
        this.context.trace("Trying to match argument '{}'", currentArgument);
×
188
        if ((currentProperty != null) && (currentProperty.isExpectValue())) {
×
189
          currentProperty.setValueAsString(arg, this.context);
×
190
          if (!currentProperty.isMultiValued()) {
×
191
            currentProperty = null;
×
192
          }
193
        } else {
194
          Property<?> property = null;
×
195
          if (!endOpts) {
×
196
            property = commandlet.getOption(currentArgument.getKey());
×
197
          }
198
          if (property == null) {
×
199
            if (!valueIterator.hasNext()) {
×
200
              this.context.trace("No option or next value found");
×
201
              return false;
×
202
            }
203
            currentProperty = valueIterator.next();
×
204
            this.context.trace("Next value candidate is {}", currentProperty);
×
205
            if (currentProperty instanceof KeywordProperty keyword) {
×
206
              if (keyword.matches(arg)) {
×
207
                keyword.setValue(Boolean.TRUE);
×
208
                this.context.trace("Keyword matched");
×
209
              } else {
210
                this.context.trace("Missing keyword");
×
211
                return false;
×
212
              }
213
            } else {
214
              boolean success = currentProperty.assignValueAsString(arg, this.context, commandlet);
×
215
              if (!success && currentProperty.isRequired()) {
×
216
                return false;
×
217
              }
218
            }
219
            if ((currentProperty != null) && !currentProperty.isMultiValued()) {
×
220
              currentProperty = null;
×
221
            }
222
          } else {
223
            this.context.trace("Found option by name");
×
224
            String value = currentArgument.getValue();
×
225
            if (value != null) {
×
226
              property.setValueAsString(value, this.context);
×
227
            } else if (property instanceof BooleanProperty) {
×
228
              ((BooleanProperty) property).setValue(Boolean.TRUE);
×
229
            } else {
230
              currentProperty = property;
×
231
              if (property.isEndOptions()) {
×
232
                endOpts = true;
×
233
              }
234
              throw new UnsupportedOperationException("not implemented");
×
235
            }
236
          }
237
        }
238
      }
239
      currentArgument = currentArgument.getNext(!endOpts);
×
240
    }
241
    return commandlet.validate().isValid();
×
242
  }
243
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc