-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathCommandNode.java
More file actions
252 lines (213 loc) · 7.13 KB
/
CommandNode.java
File metadata and controls
252 lines (213 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package roj.ui;
import org.jetbrains.annotations.Nullable;
import roj.collect.ArrayList;
import roj.text.ParseException;
import roj.text.CharList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
/**
* @author Roj234
* @since 2023/11/20 15:03
*/
public abstract class CommandNode {
private final List<CommandNode> children = new ArrayList<>();
private Command impl;
private Text comment;
@Nullable
public String getName() { return null; }
public static CommandNode literal(String name) { return new Literal(name); }
public static CommandNode argument(String name, Argument<?> argument) { return new ArgumentNode(name, argument); }
public static CommandNode redirect(CommandNode node) { return new Redirect(node); }
public CharList dump(CharList sb, int depth) {
if (impl == null) {
if (children.size() == 1) {
children.get(0).dump(sb.append(' '), depth);
if (comment != null) {
sb.set(sb.length()-1, ' ');
comment.writeAnsi(sb).append('\n');
}
return sb;
}
else if (children.size() == 0) return sb.append("[**错误:不可执行**]\n");
} else if (children.isEmpty()) {
if (comment != null) comment.writeAnsi(sb.append(' '));
return sb.append('\n');
}
depth += 2;
sb.append(':');
if (comment != null) comment.writeAnsi(sb.append(' '));
sb.append('\n');
if (impl != null) sb.padEnd(' ', depth).append("[无参数]\n");
for (CommandNode child : children) {
child.dump(sb.padEnd(' ', depth), depth);
}
return sb;
}
public CommandNode executes(Command command) {
if (impl != null) throw new IllegalStateException("Already have command");
impl = command;
return this;
}
public abstract boolean apply(CommandArgList ctx, List<Completion> completions) throws ParseException;
final boolean doApply(CommandArgList ctx, List<Completion> completions) throws ParseException {
if (ctx.peekWord() == null) {
if (completions == null && impl != null) {
ctx.exec(impl);
return true;
}
}
ParseException pe = null;
for (int i = 0; i < children.size(); i++) {
CommandNode node = children.get(i);
ctx.pushStack(this);
try {
if (node.apply(ctx, completions)) {
return true;
}
} catch (ParseException e) {
pe = e;
}
ctx.popStack();
}
if (pe != null) throw pe;
return false;
}
public List<CommandNode> getChildren() { return children; }
public Command getCommand() { return impl; }
public CommandNode getRedirect() { return null; }
static final Comparator<CommandNode> sorter = (o1, o2) -> {
String n1 = o1.getName();
String n2 = o2.getName();
if (n1 == null) return n2 == null ? 0 : 1;
if (n2 == null) return -1;
int i = n1.compareTo(n2);
if (i == 0) throw new IllegalStateException("指令名称重复:"+n1);
return i;
};
public CommandNode sorted(boolean recursion) {
if (recursion) {
for (var child : children) child.sorted(true);
}
children.sort(sorter);
return this;
}
public CommandNode then(CommandNode node) {children.add(node);return this;}
public CommandNode comment(String comment) {this.comment = Text.of(comment+"\033[0m").color16(Tty.BLACK+Tty.HIGHLIGHT);return this;}
public CommandNode comment(Text comment) {this.comment = comment;return this;}
public CommandNode repeatable() {return then(new Redirect(this));}
boolean breakOn;
public CommandNode breakOn() { breakOn = true; return this; }
public int color() {return 0xFFFFFF;}
public static final class Redirect extends CommandNode {
private CommandNode redirect;
Redirect(CommandNode redirect) {this.redirect = redirect;}
@Override
public CharList dump(CharList sb, int depth) {
sb.append("redirectNode");
return super.dump(sb, depth);
}
@Override
public CommandNode sorted(boolean recursion) {return this;}
@Override
public String getName() { return redirect.getName(); }
@Override
public boolean apply(CommandArgList ctx, List<Completion> completions) throws ParseException { return redirect.apply(ctx, completions);}
@Override
public CommandNode getRedirect() { return redirect; }
public void setRedirect(CommandNode redirect) { this.redirect = redirect; }
}
private static final class Literal extends CommandNode {
private final String name;
Literal(String name) {
if (name.trim() != name || name.isEmpty()) throw new IllegalArgumentException("literal参数不能包含空格或为空");
this.name = name;
}
public String getName() { return name; }
@Override
public CharList dump(CharList sb, int depth) { return super.dump(sb.append(name), depth); }
@Override
public boolean apply(CommandArgList ctx, List<Completion> completions) throws ParseException {
if (ctx.isEndAtWordStart()) {
if (completions != null) completions.add(new Completion(name));
return false;
}
if (ctx.peekWord() == null) return false;
String s = ctx.peekWord().text();
if (!s.equals(name)) {
if (completions != null && ctx.isEndAtWordEnd() && name.startsWith(s)) completions.add(new Completion(name.substring(s.length())));
return false;
}
ctx.nextUnquotedString();
return doApply(ctx, completions) || (completions != null && breakOn);
}
}
public static final class ArgumentNode extends CommandNode {
private final String name;
private final Argument<?> argument;
ArgumentNode(String name, Argument<?> argument) { this.name = Objects.requireNonNull(name); this.argument = Objects.requireNonNull(argument); }
public String getArgumentName() { return name; }
public Argument<?> getArgument() { return argument; }
@Override
public CharList dump(CharList sb, int depth) {
return super.dump(sb.append(argument.format(name, 1)), depth);
}
public int color() {return argument.color();}
@Override
public boolean apply(CommandArgList ctx, List<Completion> completions) throws ParseException {
if (ctx.isEndAtWordStart()) {
if (completions != null) {
makeExample(completions);
} else {
try {
Object o = argument.parse(ctx, null);
ctx.setArgument(name, o);
return doApply(ctx, null);
} catch (Exception e) {
ctx.failedOn(name, argument);
return false;
}
}
return false;
}
boolean shouldPop = true;
ctx.pushStack(this);
Object o;
block:
try {
o = argument.parse(ctx, completions);
if (o == null) {
if (completions != null && ctx.isEndAtWordStart()) {
makeExample(completions);
}
break block;
}
ctx.setArgument(name, o);
} catch (ParseException e) {
ctx.popStack();
shouldPop = false;
if (completions == null) throw e;
}
try {
return doApply(ctx, completions);
} finally {
if (shouldPop) ctx.popStack();
}
}
private void makeExample(List<Completion> completions) {
int size = completions.size();
argument.example(completions);
if (completions.size() == size) {
var tip = new Completion(argument.format(name, 0));
tip.isTip = true;
completions.add(tip);
}
var paramDesc = new Text("参数:"+argument.format(name, 2));
for (; size < completions.size(); size++) {
Completion completion = completions.get(size);
if (completion.description == null)
completion.description = paramDesc;
}
}
}
}