1 package org.cyclopsgroup.jcli.jline;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.List;
6 import java.util.concurrent.atomic.AtomicBoolean;
7 import java.util.concurrent.atomic.AtomicInteger;
8 import org.cyclopsgroup.caff.token.TokenEvent;
9 import org.cyclopsgroup.caff.token.TokenEventHandler;
10 import org.cyclopsgroup.caff.token.ValueTokenizer;
11 import org.cyclopsgroup.jcli.ArgumentProcessor;
12 import org.cyclopsgroup.jcli.AutoCompletable;
13 import org.cyclopsgroup.jcli.spi.Option;
14 import org.cyclopsgroup.jcli.spi.ParsingContext;
15 import org.jline.reader.Candidate;
16 import org.jline.reader.Completer;
17 import org.jline.reader.LineReader;
18 import org.jline.reader.ParsedLine;
19 import com.google.common.base.Preconditions;
20 import com.google.common.base.Strings;
21
22
23
24
25
26
27 public class CliCompletor implements Completer {
28 private static List<String> filterList(List<String> list, String prefix) {
29 if (Strings.isNullOrEmpty(prefix)) {
30 return list;
31 }
32 List<String> results = new ArrayList<String>();
33 for (String item : list) {
34 if (item.startsWith(prefix)) {
35 results.add(item);
36 }
37 }
38 return results;
39 }
40
41 private final ParsingContext context;
42
43 private final AutoCompletable completable;
44
45 private final ValueTokenizer tokenizer;
46
47
48
49
50
51 public CliCompletor(final Object cliBean, final ValueTokenizer tokenizer) {
52 Preconditions.checkNotNull(cliBean, "Cli bean can't be null.");
53 Preconditions.checkNotNull(tokenizer, "String tokenizer can't be null.");
54 context = ArgumentProcessor.forType(cliBean.getClass()).createParsingContext();
55 if (cliBean instanceof AutoCompletable) {
56 this.completable = (AutoCompletable) cliBean;
57 } else {
58 this.completable = new AutoCompletable() {
59 public List<String> suggestArgument(String partialArgument) {
60 return Collections.emptyList();
61 }
62
63 public List<String> suggestOption(String optionName, String partialOption) {
64 return Collections.emptyList();
65 }
66 };
67 }
68 this.tokenizer = tokenizer;
69 }
70
71 public void complete(LineReader reader, ParsedLine line, List<Candidate> suggestions) {
72 String command = line.line();
73 ArgumentsInspectortor.html#ArgumentsInspector">ArgumentsInspector inspector = new ArgumentsInspector(context);
74 final AtomicBoolean terminated = new AtomicBoolean(true);
75 final AtomicInteger lastWordStart = new AtomicInteger(0);
76 if (!Strings.isNullOrEmpty(command)) {
77 final List<String> args = new ArrayList<String>();
78 tokenizer.parse(command, new TokenEventHandler() {
79
80 public void handleEvent(TokenEvent ev) {
81 args.add(ev.getToken());
82 terminated.set(ev.isTerminated());
83 lastWordStart.set(ev.getStart());
84 }
85 });
86 for (String arg : args) {
87 inspector.consume(arg);
88 }
89 if (terminated.get()) {
90 inspector.end();
91 }
92 }
93
94
95
96 List<String> candidates = new ArrayList<String>();
97 switch (inspector.getState()) {
98 case READY:
99 for (Option o : inspector.getRemainingOptions()) {
100 candidates.add("-" + o.getName());
101 }
102 Collections.sort(candidates);
103 candidates.addAll(suggestArguments(null));
104 break;
105 case OPTION:
106 case LONG_OPTION:
107 candidates.addAll(suggestOptionNames(inspector, inspector.getCurrentValue()));
108 break;
109 case OPTION_VALUE:
110 candidates
111 .addAll(suggestOptionValue(inspector.getCurrentOption(), inspector.getCurrentValue()));
112 break;
113 case ARGUMENT:
114 candidates.addAll(suggestArguments(inspector.getCurrentValue()));
115 }
116 for (String candidate : candidates) {
117 suggestions.add(new Candidate(tokenizer.escape(candidate)));
118 }
119 }
120
121 private List<String> suggestArguments(String partialArgument) {
122 List<String> results;
123 if (Strings.isNullOrEmpty(partialArgument)) {
124 results = completable.suggestArgument(null);
125 } else {
126 results = completable.suggestArgument(partialArgument);
127 if (results == null) {
128 results = filterList(completable.suggestArgument(null), partialArgument);
129 }
130 }
131 if (results == null) {
132 results = Collections.emptyList();
133 } else {
134 results = new ArrayList<String>(results);
135 Collections.sort(results);
136 }
137 return results;
138 }
139
140 private List<String> suggestOptionNames(ArgumentsInspector inspector, String value) {
141 List<String> results = new ArrayList<String>();
142 for (Option o : inspector.getRemainingOptions()) {
143 if (value.startsWith("--") && o.getLongName() != null
144 && ("--" + o.getLongName()).startsWith(value)) {
145 results.add("--" + o.getLongName());
146 } else if (value.startsWith("-") && ("-" + o.getName()).startsWith(value)) {
147 results.add("-" + o.getName());
148 }
149 }
150 Collections.sort(results);
151 return results;
152 }
153
154 private List<String> suggestOptionValue(Option option, String partialValue) {
155 List<String> results;
156 if (Strings.isNullOrEmpty(partialValue)) {
157 results = completable.suggestOption(option.getName(), null);
158 } else {
159 results = completable.suggestOption(option.getName(), partialValue);
160 if (results == null) {
161 results = filterList(completable.suggestOption(option.getName(), null), partialValue);
162 }
163 }
164 if (results == null) {
165 results = Collections.emptyList();
166 } else {
167 results = new ArrayList<String>(results);
168 Collections.sort(results);
169 }
170 return results;
171 }
172 }