├── BenchmarkResult ├── Execute-2018-10-10-22-52.txt ├── Execute-2019-1-25-22-9.txt ├── Parsing-2018-10-10-22-52.txt └── Parsing-2019-1-25-22-9.txt ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── index.d.ts ├── index.js └── lib │ ├── AmbiguityConsumer.js │ ├── Command.js │ ├── CommandDispatcher.js │ ├── ImmutableStringReader.js │ ├── LiteralMessage.js │ ├── Message.js │ ├── ParseResults.js │ ├── Predicate.js │ ├── Primitive.js │ ├── RedirectModifier.js │ ├── ResultConsumer.js │ ├── SingleRedirectModifier.js │ ├── StringReader.js │ ├── arguments │ ├── ArgumentType.js │ ├── BoolArgumentType.js │ ├── FloatArgumentType.js │ ├── IntegerArgumentType.js │ └── StringArgumentType.js │ ├── builder │ ├── ArgumentBuilder.js │ ├── LiteralArgumentBuilder.js │ └── RequiredArgumentBuilder.js │ ├── context │ ├── CommandContext.js │ ├── CommandContextBuilder.js │ ├── ParsedArgument.js │ ├── ParsedCommandNode.js │ ├── StringRange.js │ └── SuggestionContext.js │ ├── exceptions │ ├── BuiltInExceptionProvider.js │ ├── BuiltInExceptions.js │ ├── CommandExceptionType.js │ ├── CommandSyntaxException.js │ ├── Dynamic2CommandExceptionType.js │ ├── DynamicCommandExceptionType.js │ └── SimpleCommandExceptionType.js │ ├── suggestion │ ├── IntegerSuggestion.js │ ├── Suggestion.js │ ├── SuggestionProvider.js │ ├── Suggestions.js │ └── SuggestionsBuilder.js │ ├── tree │ ├── ArgumentCommandNode.js │ ├── CommandNode.js │ ├── LiteralCommandNode.js │ └── RootCommandNode.js │ └── util │ └── isEqual.js ├── example ├── fill.js └── fill.ts ├── index.js ├── package.json ├── src ├── index.ts └── lib │ ├── AmbiguityConsumer.ts │ ├── Command.ts │ ├── CommandDispatcher.ts │ ├── ImmutableStringReader.ts │ ├── LiteralMessage.ts │ ├── Message.ts │ ├── ParseResults.ts │ ├── Predicate.ts │ ├── RedirectModifier.ts │ ├── ResultConsumer.ts │ ├── SingleRedirectModifier.ts │ ├── StringReader.ts │ ├── arguments │ ├── ArgumentType.ts │ ├── BoolArgumentType.ts │ ├── FloatArgumentType.ts │ ├── IntegerArgumentType.ts │ └── StringArgumentType.ts │ ├── builder │ ├── ArgumentBuilder.ts │ ├── LiteralArgumentBuilder.ts │ └── RequiredArgumentBuilder.ts │ ├── context │ ├── CommandContext.ts │ ├── CommandContextBuilder.ts │ ├── ParsedArgument.ts │ ├── ParsedCommandNode.ts │ ├── StringRange.ts │ └── SuggestionContext.ts │ ├── exceptions │ ├── BuiltInExceptionProvider.ts │ ├── BuiltInExceptions.ts │ ├── CommandExceptionType.ts │ ├── CommandSyntaxException.ts │ ├── DynamicCommandExceptionType.ts │ └── SimpleCommandExceptionType.ts │ ├── suggestion │ ├── IntegerSuggestion.ts │ ├── Suggestion.ts │ ├── SuggestionProvider.ts │ ├── Suggestions.ts │ └── SuggestionsBuilder.ts │ ├── tree │ ├── ArgumentCommandNode.ts │ ├── CommandNode.ts │ ├── LiteralCommandNode.ts │ └── RootCommandNode.ts │ └── util │ └── isEqual.ts └── test ├── CommandDispatcherTest.ts ├── CommandDispatcherUsagesTest.ts ├── CommandSuggestionsTest.ts ├── StringReaderTest.ts ├── arguments ├── BoolArgumentTypeTest.ts ├── FloatArgumentTypeTest.ts ├── IntegerArgumentTypeTest.ts └── StringArgumentTypeTest.ts ├── benchmarks ├── ExecuteBenchmark.js └── ParsingBenchmark.js ├── builder ├── ArgumentBuilderTest.ts ├── LiteralArgumentBuilderTest.ts └── RequiredArgumentBuilderTest.ts ├── context ├── CommandContextTest.ts └── ParsedArgumentTest.ts ├── exceptions ├── DynamicCommandSyntaxExceptionTypeTest.ts └── SimpleCommandSyntaxExceptionTypeTest.ts ├── suggestion ├── SuggestionBuilderTest.ts ├── SuggestionTest.ts └── SuggestionsTest.ts ├── tree ├── AbstractCommandNodeTest.ts ├── ArgumentCommandNodeTest.ts ├── LiteralCommandNodeTest.ts └── RootCommandNodeTest.ts └── utils └── testEquality.ts /BenchmarkResult/Execute-2018-10-10-22-52.txt: -------------------------------------------------------------------------------- 1 | ┌─────────────────────────┬────────────┬────────────┬────────────┐ 2 | │ NAME │ AVG │ MIN │ MAX │ 3 | ╞═════════════════════════╪════════════╪════════════╪════════════╡ 4 | │ execute simple │ 4204 ns │ 1399 ns │ 436207 ns │ 5 | ├─────────────────────────┼────────────┼────────────┼────────────┤ 6 | │ execute single redirect │ 8069 ns │ 466 ns │ 1387466 ns │ 7 | ├─────────────────────────┼────────────┼────────────┼────────────┤ 8 | │ execute forked redirect │ 11379 ns │ 933 ns │ 2990936 ns │ 9 | └─────────────────────────┴────────────┴────────────┴────────────┘ 10 | 11 | -------------------------------------------------------------------------------- /BenchmarkResult/Execute-2019-1-25-22-9.txt: -------------------------------------------------------------------------------- 1 | ┌─────────────────────────┬────────────┬────────────┬────────────┐ 2 | │ NAME │ AVG │ MIN │ MAX │ 3 | ╞═════════════════════════╪════════════╪════════════╪════════════╡ 4 | │ execute simple │ 6857 ns │ 1799 ns │ 1270100 ns │ 5 | ├─────────────────────────┼────────────┼────────────┼────────────┤ 6 | │ execute single redirect │ 9696 ns │ 499 ns │ 1612700 ns │ 7 | ├─────────────────────────┼────────────┼────────────┼────────────┤ 8 | │ execute forked redirect │ 12179 ns │ 999 ns │ 2724401 ns │ 9 | └─────────────────────────┴────────────┴────────────┴────────────┘ 10 | 11 | -------------------------------------------------------------------------------- /BenchmarkResult/Parsing-2018-10-10-22-52.txt: -------------------------------------------------------------------------------- 1 | ┌─────────────┬────────────┬────────────┬────────────┐ 2 | │ NAME │ AVG │ MIN │ MAX │ 3 | ╞═════════════╪════════════╪════════════╪════════════╡ 4 | │ parse a 1 i │ 29587 ns │ 1866 ns │ 3199943 ns │ 5 | ├─────────────┼────────────┼────────────┼────────────┤ 6 | │ parse c │ 4052 ns │ 933 ns │ 285051 ns │ 7 | ├─────────────┼────────────┼────────────┼────────────┤ 8 | │ parse k 1 i │ 18342 ns │ 2332 ns │ 3216737 ns │ 9 | ├─────────────┼────────────┼────────────┼────────────┤ 10 | │ parse _ │ 3445 ns │ 933 ns │ 492657 ns │ 11 | └─────────────┴────────────┴────────────┴────────────┘ 12 | 13 | -------------------------------------------------------------------------------- /BenchmarkResult/Parsing-2019-1-25-22-9.txt: -------------------------------------------------------------------------------- 1 | ┌─────────────┬────────────┬────────────┬────────────┐ 2 | │ NAME │ AVG │ MIN │ MAX │ 3 | ╞═════════════╪════════════╪════════════╪════════════╡ 4 | │ parse a 1 i │ 34683 ns │ 2099 ns │ 2999500 ns │ 5 | ├─────────────┼────────────┼────────────┼────────────┤ 6 | │ parse c │ 5036 ns │ 1100 ns │ 351200 ns │ 7 | ├─────────────┼────────────┼────────────┼────────────┤ 8 | │ parse k 1 i │ 37809 ns │ 3500 ns │ 6901201 ns │ 9 | ├─────────────┼────────────┼────────────┼────────────┤ 10 | │ parse _ │ 6680 ns │ 1000 ns │ 668301 ns │ 11 | └─────────────┴────────────┴────────────┴────────────┘ 12 | 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | ___ 3 | ## 0.0.3 4 | - Add support for Typescript 5 | - Add CHANGELOG file 6 | ## 0.0.4 7 | - Update README 8 | - Clearer d.ts 9 | - Add an example folder 10 | ## 0.0.5 11 | - Update index.d.ts to clear some typescript error 12 | - Remove unnecessary d.ts 13 | ## 0.0.6 14 | - Update d.ts 15 | - Change Set to Array (IterableIterator -> Iterable) 16 | ## 0.0.7 17 | - Fix Exception classes required 0 argument in constructor 18 | ## 0.0.8 19 | - Export a default dispatcher `dispatcher = new CommandDispatcher()` 20 | ## 0.0.9 21 | - Remove `Dynamic2CommandExceptionType` 22 | - Make `DynamicCommandExceptionType` recieve any argument 23 | - Clearer declaration (d.ts) 24 | ## 0.0.10 25 | - Add constructor to `StringReader` in d.ts (Opps~) 26 | - Change `CommandSyntaxException` to more suitable for Javascript 27 | - Make `listSuggestions()` and `getExamples()` in `interface ArgumentType` optional 28 | ## 0.0.11 29 | - Replace `enum Primitive` with default JS Function `Number() String() Boolean()` 30 | - StringReader now allow scientific notation of numbers ex: `12e-3` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 remtori 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node-Brigadier 2 | ___ 3 | This is a nodejs version of Mojang's Brigadier library. 4 | >Brigadier is a command parser & dispatcher, designed and developed for Minecraft: Java Edition and now freely available for use elsewhere under the MIT license. 5 | # Installation 6 | ___ 7 | ``` 8 | npm install node-brigadier --save 9 | ``` 10 | # Usage 11 | ___ 12 | ## Dispatch a command 13 | ```javascript 14 | const { CommandDispatcher, literal, argument, string, Suggestions } = require("node-brigadier") 15 | 16 | class BlockPos { 17 | constructor(x = 0, y = 0, z = 0) { 18 | this.x = x; this.y = y; this.z = z; 19 | } 20 | parse(reader) { 21 | this.x = reader.readInt(); 22 | reader.skip(); 23 | this.y = reader.readInt(); 24 | reader.skip(); 25 | this.z = reader.readInt(); 26 | return this; 27 | } 28 | listSuggestions(context, builder) { 29 | return Suggestions.empty(); 30 | } 31 | getExamples() { 32 | return [ 33 | "1 2 3" 34 | ] 35 | } 36 | } 37 | 38 | const dispatcher = new CommandDispatcher(); 39 | 40 | dispatcher.register( 41 | literal("fill").then( 42 | argument("pos1", new BlockPos()).then( 43 | argument("pos2", new BlockPos()).then( 44 | argument("block", string()).executes(context => { 45 | console.log(context.getArgument("pos1", BlockPos)) 46 | console.log(context.getArgument("pos2", BlockPos)) 47 | console.log(context.getArgument("block", /*String*/ 3)) 48 | return 0; 49 | }) 50 | ) 51 | ) 52 | ) 53 | ) 54 | 55 | const parsedCommand = dispatcher.parse("fill 3 4 5 10 11 12 air", {}) 56 | try { 57 | dispatcher.execute(parsedCommand); 58 | } catch (ex) { 59 | console.error(ex.getMessage()); 60 | } 61 | 62 | // Console 63 | // BlockPos { x: 3, y: 4, z: 5 } 64 | // BlockPos { x: 10, y: 11, z: 12 } 65 | // air 66 | ``` -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | var __importStar = (this && this.__importStar) || function (mod) { 6 | if (mod && mod.__esModule) return mod; 7 | var result = {}; 8 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 9 | result["default"] = mod; 10 | return result; 11 | }; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | const CommandDispatcher_1 = __importDefault(require("./lib/CommandDispatcher")); 14 | const LiteralMessage_1 = __importDefault(require("./lib/LiteralMessage")); 15 | const ParseResults_1 = __importDefault(require("./lib/ParseResults")); 16 | const StringReader_1 = __importDefault(require("./lib/StringReader")); 17 | const ArgumentType_1 = require("./lib/arguments/ArgumentType"); 18 | const LiteralArgumentBuilder_1 = __importStar(require("./lib/builder/LiteralArgumentBuilder")); 19 | const RequiredArgumentBuilder_1 = __importStar(require("./lib/builder/RequiredArgumentBuilder")); 20 | const CommandContext_1 = __importDefault(require("./lib/context/CommandContext")); 21 | const CommandContextBuilder_1 = __importDefault(require("./lib/context/CommandContextBuilder")); 22 | const ParsedArgument_1 = __importDefault(require("./lib/context/ParsedArgument")); 23 | const ParsedCommandNode_1 = __importDefault(require("./lib/context/ParsedCommandNode")); 24 | const StringRange_1 = __importDefault(require("./lib/context/StringRange")); 25 | const SuggestionContext_1 = __importDefault(require("./lib/context/SuggestionContext")); 26 | const CommandSyntaxException_1 = __importDefault(require("./lib/exceptions/CommandSyntaxException")); 27 | const DynamicCommandExceptionType_1 = __importDefault(require("./lib/exceptions/DynamicCommandExceptionType")); 28 | const SimpleCommandExceptionType_1 = __importDefault(require("./lib/exceptions/SimpleCommandExceptionType")); 29 | const Suggestion_1 = __importDefault(require("./lib/suggestion/Suggestion")); 30 | const Suggestions_1 = __importDefault(require("./lib/suggestion/Suggestions")); 31 | const SuggestionsBuilder_1 = __importDefault(require("./lib/suggestion/SuggestionsBuilder")); 32 | const ArgumentCommandNode_1 = __importDefault(require("./lib/tree/ArgumentCommandNode")); 33 | const LiteralCommandNode_1 = __importDefault(require("./lib/tree/LiteralCommandNode")); 34 | const RootCommandNode_1 = __importDefault(require("./lib/tree/RootCommandNode")); 35 | const { word, string, greedyString, bool, integer, float } = ArgumentType_1.DefaultType; 36 | module.exports = { 37 | dispatcher: new CommandDispatcher_1.default(), 38 | word, string, greedyString, bool, integer, float, 39 | literal: LiteralArgumentBuilder_1.literal, argument: RequiredArgumentBuilder_1.argument, 40 | CommandDispatcher: CommandDispatcher_1.default, 41 | LiteralMessage: LiteralMessage_1.default, 42 | ParseResults: ParseResults_1.default, 43 | StringReader: StringReader_1.default, 44 | LiteralArgumentBuilder: LiteralArgumentBuilder_1.default, 45 | RequiredArgumentBuilder: RequiredArgumentBuilder_1.default, 46 | CommandContext: CommandContext_1.default, 47 | CommandContextBuilder: CommandContextBuilder_1.default, 48 | ParsedArgument: ParsedArgument_1.default, 49 | ParsedCommandNode: ParsedCommandNode_1.default, 50 | StringRange: StringRange_1.default, 51 | SuggestionsContext: SuggestionContext_1.default, 52 | CommandSyntaxException: CommandSyntaxException_1.default, 53 | SimpleCommandExceptionType: SimpleCommandExceptionType_1.default, 54 | DynamicCommandExceptionType: DynamicCommandExceptionType_1.default, 55 | Suggestion: Suggestion_1.default, 56 | Suggestions: Suggestions_1.default, 57 | SuggestionsBuilder: SuggestionsBuilder_1.default, 58 | ArgumentCommandNode: ArgumentCommandNode_1.default, 59 | LiteralCommandNode: LiteralCommandNode_1.default, 60 | RootCommandNode: RootCommandNode_1.default 61 | }; 62 | -------------------------------------------------------------------------------- /dist/lib/AmbiguityConsumer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/Command.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/ImmutableStringReader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/LiteralMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class LiteralMessage { 4 | constructor(str) { 5 | this.str = str; 6 | } 7 | getString() { 8 | return this.str; 9 | } 10 | toString() { 11 | return this.str; 12 | } 13 | } 14 | exports.default = LiteralMessage; 15 | -------------------------------------------------------------------------------- /dist/lib/Message.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/ParseResults.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const StringReader_1 = __importDefault(require("./StringReader")); 7 | class ParseResults { 8 | constructor(context, reader, exceptions) { 9 | this.context = context; 10 | this.reader = reader || new StringReader_1.default(""); 11 | this.exceptions = exceptions || new Map(); 12 | } 13 | getContext() { 14 | return this.context; 15 | } 16 | getReader() { 17 | return this.reader; 18 | } 19 | getExceptions() { 20 | return this.exceptions; 21 | } 22 | } 23 | exports.default = ParseResults; 24 | -------------------------------------------------------------------------------- /dist/lib/Predicate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/Primitive.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/RedirectModifier.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/ResultConsumer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/SingleRedirectModifier.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/StringReader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const CommandSyntaxException_1 = __importDefault(require("./exceptions/CommandSyntaxException")); 7 | const SYNTAX_ESCAPE = '\\'; 8 | const SYNTAX_QUOTE = '\"'; 9 | class StringReader { 10 | constructor(other) { 11 | this.cursor = 0; 12 | if (typeof other === "string") { 13 | this.string = other; 14 | } 15 | else { 16 | this.string = other.string; 17 | this.cursor = other.cursor; 18 | } 19 | } 20 | getString() { 21 | return this.string; 22 | } 23 | setCursor(cursor) { 24 | this.cursor = cursor; 25 | } 26 | getRemainingLength() { 27 | return (this.string.length - this.cursor); 28 | } 29 | getTotalLength() { 30 | return this.string.length; 31 | } 32 | getCursor() { 33 | return this.cursor; 34 | } 35 | getRead() { 36 | return this.string.substring(0, this.cursor); 37 | } 38 | getRemaining() { 39 | return this.string.substring(this.cursor); 40 | } 41 | canRead(length = 1) { 42 | return this.cursor + length <= this.string.length; 43 | } 44 | peek(offset = 0) { 45 | return this.string.charAt(this.cursor + offset); 46 | } 47 | read() { 48 | return this.string.charAt(this.cursor++); 49 | } 50 | skip() { 51 | this.cursor++; 52 | } 53 | static isAllowedNumber(c) { 54 | return c >= '0' && c <= '9' || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E'; 55 | } 56 | skipWhitespace() { 57 | while ((this.canRead() && /\s/.test(this.peek()))) { 58 | this.skip(); 59 | } 60 | } 61 | readInt() { 62 | let start = this.cursor; 63 | while (this.canRead() && StringReader.isAllowedNumber(this.peek())) { 64 | this.skip(); 65 | } 66 | let number = this.string.substring(start, this.cursor); 67 | if (number.length === 0) { 68 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerExpectedInt().createWithContext(this); 69 | } 70 | const result = parseInt(number); 71 | if (isNaN(result) || result !== parseFloat(number)) { 72 | this.cursor = start; 73 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerInvalidInt().createWithContext(this, number); 74 | } 75 | else 76 | return result; 77 | } 78 | readFloat() { 79 | let start = this.cursor; 80 | while ((this.canRead() && StringReader.isAllowedNumber(this.peek()))) { 81 | this.skip(); 82 | } 83 | let number = this.string.substring(start, this.cursor); 84 | if (number.length === 0) { 85 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerExpectedFloat().createWithContext(this); 86 | } 87 | const result = parseFloat(number); 88 | const strictParseFloatTest = parseFloat(number.substring(result.toString().length, this.cursor)); 89 | if (isNaN(result) || (!isNaN(strictParseFloatTest) && 90 | strictParseFloatTest !== 0)) { 91 | this.cursor = start; 92 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerInvalidFloat().createWithContext(this, number); 93 | } 94 | else 95 | return result; 96 | } 97 | static isAllowedInUnquotedString(c) { 98 | return c >= '0' && c <= '9' 99 | || c >= 'A' && c <= 'Z' 100 | || c >= 'a' && c <= 'z' 101 | || c == '_' || c == '-' 102 | || c == '.' || c == '+'; 103 | } 104 | readUnquotedString() { 105 | let start = this.cursor; 106 | while (this.canRead() && StringReader.isAllowedInUnquotedString(this.peek())) { 107 | this.skip(); 108 | } 109 | return this.string.substring(start, this.cursor); 110 | } 111 | readQuotedString() { 112 | if (!this.canRead()) { 113 | return ""; 114 | } 115 | else if ((this.peek() != SYNTAX_QUOTE)) { 116 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerExpectedStartOfQuote().createWithContext(this); 117 | } 118 | this.skip(); 119 | let result = ""; 120 | let escaped = false; 121 | while (this.canRead()) { 122 | let c = this.read(); 123 | if (escaped) { 124 | if (c == SYNTAX_QUOTE || c == SYNTAX_ESCAPE) { 125 | result += c; 126 | escaped = false; 127 | } 128 | else { 129 | this.setCursor(this.getCursor() - 1); 130 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerInvalidEscape().createWithContext(this, c); 131 | } 132 | } 133 | else if (c == SYNTAX_ESCAPE) { 134 | escaped = true; 135 | } 136 | else if (c == SYNTAX_QUOTE) { 137 | return result; 138 | } 139 | else { 140 | result += c; 141 | } 142 | } 143 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerExpectedEndOfQuote().createWithContext(this); 144 | } 145 | readString() { 146 | if (this.canRead() && (this.peek() === SYNTAX_QUOTE)) { 147 | return this.readQuotedString(); 148 | } 149 | else { 150 | return this.readUnquotedString(); 151 | } 152 | } 153 | readBoolean() { 154 | let start = this.cursor; 155 | let value = this.readString(); 156 | if (value.length === 0) { 157 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerExpectedBool().createWithContext(this); 158 | } 159 | if (value === "true") { 160 | return true; 161 | } 162 | else if (value === "false") { 163 | return false; 164 | } 165 | else { 166 | this.cursor = start; 167 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerInvalidBool().createWithContext(this, value); 168 | } 169 | } 170 | expect(c) { 171 | if (!this.canRead() || this.peek() !== c) { 172 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.readerExpectedSymbol().createWithContext(this, c); 173 | } 174 | this.skip(); 175 | } 176 | } 177 | exports.default = StringReader; 178 | -------------------------------------------------------------------------------- /dist/lib/arguments/ArgumentType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const BoolArgumentType_1 = __importDefault(require("./BoolArgumentType")); 7 | const IntegerArgumentType_1 = __importDefault(require("./IntegerArgumentType")); 8 | const FloatArgumentType_1 = __importDefault(require("./FloatArgumentType")); 9 | const StringArgumentType_1 = __importDefault(require("./StringArgumentType")); 10 | exports.DefaultType = { 11 | bool: BoolArgumentType_1.default.bool, 12 | integer: IntegerArgumentType_1.default.integer, 13 | float: FloatArgumentType_1.default.float, 14 | word: StringArgumentType_1.default.word, 15 | string: StringArgumentType_1.default.string, 16 | greedyString: StringArgumentType_1.default.greedyString 17 | }; 18 | -------------------------------------------------------------------------------- /dist/lib/arguments/BoolArgumentType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const EXAMPLES = ["true", "false"]; 4 | class BoolArgumentType { 5 | constructor() { 6 | } 7 | static bool() { 8 | return new BoolArgumentType(); 9 | } 10 | static getBool(context, name) { 11 | return context.getArgument(name, Boolean); 12 | } 13 | parse(reader) { 14 | return reader.readBoolean(); 15 | } 16 | listSuggestions(context, builder) { 17 | if ("true".startsWith(builder.getRemaining().toLowerCase())) { 18 | builder.suggest("true"); 19 | } 20 | if ("false".startsWith(builder.getRemaining().toLowerCase())) { 21 | builder.suggest("false"); 22 | } 23 | return builder.buildPromise(); 24 | } 25 | getExamples() { 26 | return EXAMPLES; 27 | } 28 | } 29 | exports.default = BoolArgumentType; 30 | -------------------------------------------------------------------------------- /dist/lib/arguments/FloatArgumentType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const CommandSyntaxException_1 = __importDefault(require("../exceptions/CommandSyntaxException")); 7 | const EXAMPLES = ["0", "1.2", ".5", "-1", "-.5", "-1234.56"]; 8 | class FloatArgumentType { 9 | constructor(minimum, maximum) { 10 | this.minimum = minimum; 11 | this.maximum = maximum; 12 | } 13 | static float(min = -Infinity, max = Infinity) { 14 | return new FloatArgumentType(min, max); 15 | } 16 | static getFloat(context, name) { 17 | return context.getArgument(name, Number); 18 | } 19 | getMinimum() { 20 | return this.minimum; 21 | } 22 | getMaximum() { 23 | return this.maximum; 24 | } 25 | parse(reader) { 26 | let start = reader.getCursor(); 27 | let result = reader.readFloat(); 28 | if (result < this.minimum) { 29 | reader.setCursor(start); 30 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.integerTooLow().createWithContext(reader, result, this.minimum); 31 | } 32 | if (result > this.maximum) { 33 | reader.setCursor(start); 34 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.integerTooHigh().createWithContext(reader, result, this.maximum); 35 | } 36 | return result; 37 | } 38 | equals(o) { 39 | if (this === o) 40 | return true; 41 | if (!(o instanceof FloatArgumentType)) 42 | return false; 43 | return this.maximum == o.maximum && this.minimum == o.minimum; 44 | } 45 | toString() { 46 | if (this.minimum === -Infinity && this.maximum === Infinity) { 47 | return "float()"; 48 | } 49 | else if (this.maximum == Infinity) { 50 | return "float(" + this.minimum + ")"; 51 | } 52 | else { 53 | return "float(" + this.minimum + ", " + this.maximum + ")"; 54 | } 55 | } 56 | getExamples() { 57 | return EXAMPLES; 58 | } 59 | } 60 | exports.default = FloatArgumentType; 61 | -------------------------------------------------------------------------------- /dist/lib/arguments/IntegerArgumentType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const CommandSyntaxException_1 = __importDefault(require("../exceptions/CommandSyntaxException")); 7 | const EXAMPLES = ["0", "123", "-123"]; 8 | class IntegerArgumentType { 9 | constructor(minimum, maximum) { 10 | this.minimum = minimum; 11 | this.maximum = maximum; 12 | } 13 | static integer(min = -Infinity, max = Infinity) { 14 | return new IntegerArgumentType(min, max); 15 | } 16 | static getInteger(context, name) { 17 | return context.getArgument(name, Number); 18 | } 19 | getMinimum() { 20 | return this.minimum; 21 | } 22 | getMaximum() { 23 | return this.maximum; 24 | } 25 | parse(reader) { 26 | let start = reader.getCursor(); 27 | let result = reader.readInt(); 28 | if (result < this.minimum) { 29 | reader.setCursor(start); 30 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.integerTooLow().createWithContext(reader, result, this.minimum); 31 | } 32 | if (result > this.maximum) { 33 | reader.setCursor(start); 34 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.integerTooHigh().createWithContext(reader, result, this.maximum); 35 | } 36 | return result; 37 | } 38 | equals(o) { 39 | if (this === o) 40 | return true; 41 | if (!(o instanceof IntegerArgumentType)) 42 | return false; 43 | return this.maximum == o.maximum && this.minimum == o.minimum; 44 | } 45 | toString() { 46 | if (this.minimum === -Infinity && this.maximum === Infinity) { 47 | return "integer()"; 48 | } 49 | else if (this.maximum == Infinity) { 50 | return "integer(" + this.minimum + ")"; 51 | } 52 | else { 53 | return "integer(" + this.minimum + ", " + this.maximum + ")"; 54 | } 55 | } 56 | getExamples() { 57 | return EXAMPLES; 58 | } 59 | } 60 | exports.default = IntegerArgumentType; 61 | -------------------------------------------------------------------------------- /dist/lib/arguments/StringArgumentType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const StringReader_1 = __importDefault(require("../StringReader")); 7 | var StringType; 8 | (function (StringType) { 9 | StringType["SINGLE_WORD"] = "words_with_underscores"; 10 | StringType["QUOTABLE_PHRASE"] = "\"quoted phrase\""; 11 | StringType["GREEDY_PHRASE"] = "words with spaces"; 12 | })(StringType = exports.StringType || (exports.StringType = {})); 13 | class StringArgumentType { 14 | constructor(type) { 15 | this.type = type; 16 | } 17 | static word() { 18 | return new StringArgumentType(StringType.SINGLE_WORD); 19 | } 20 | static string() { 21 | return new StringArgumentType(StringType.QUOTABLE_PHRASE); 22 | } 23 | static greedyString() { 24 | return new StringArgumentType(StringType.GREEDY_PHRASE); 25 | } 26 | static getString(context, name) { 27 | return context.getArgument(name, String); 28 | } 29 | getType() { 30 | return this.type; 31 | } 32 | parse(reader) { 33 | if (this.type == StringType.GREEDY_PHRASE) { 34 | let text = reader.getRemaining(); 35 | reader.setCursor(reader.getTotalLength()); 36 | return text; 37 | } 38 | else if (this.type == StringType.SINGLE_WORD) { 39 | return reader.readUnquotedString(); 40 | } 41 | else { 42 | return reader.readString(); 43 | } 44 | } 45 | toString() { 46 | return "string()"; 47 | } 48 | static escapeIfRequired(input) { 49 | for (let c of input) { 50 | if (!StringReader_1.default.isAllowedInUnquotedString(c)) { 51 | return StringArgumentType.escape(input); 52 | } 53 | } 54 | return input; 55 | } 56 | static escape(input) { 57 | let result = "\""; 58 | for (let i = 0; i < input.length; i++) { 59 | const c = input.charAt(i); 60 | if (c == '\\' || c == '"') { 61 | result += '\\'; 62 | } 63 | result += c; 64 | } 65 | result += "\""; 66 | return result; 67 | } 68 | } 69 | exports.default = StringArgumentType; 70 | -------------------------------------------------------------------------------- /dist/lib/builder/ArgumentBuilder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const CommandNode_1 = __importDefault(require("../tree/CommandNode")); 7 | const RootCommandNode_1 = __importDefault(require("../tree/RootCommandNode")); 8 | class ArgumentBuilder { 9 | constructor() { 10 | this.args = new RootCommandNode_1.default(); 11 | this.modifier = null; 12 | } 13 | then(arg) { 14 | if (!(this.target == null)) { 15 | throw new Error("Cannot add children to a redirected node"); 16 | } 17 | if (arg instanceof CommandNode_1.default) 18 | this.args.addChild(arg); 19 | else 20 | this.args.addChild(arg.build()); 21 | return this.getThis(); 22 | } 23 | getArguments() { 24 | return this.args.getChildren(); 25 | } 26 | executes(command) { 27 | this.command = command; 28 | return this.getThis(); 29 | } 30 | getCommand() { 31 | return this.command; 32 | } 33 | requires(requirement) { 34 | this.requirement = requirement; 35 | return this.getThis(); 36 | } 37 | getRequirement() { 38 | return this.requirement; 39 | } 40 | redirect(target, modifier) { 41 | return this.forward(target, modifier == null ? null : (o) => [modifier.apply(o)], false); 42 | } 43 | fork(target, modifier) { 44 | return this.forward(target, modifier, true); 45 | } 46 | forward(target, modifier, fork) { 47 | if (this.args.getChildrenCount() > 0) { 48 | throw new Error("Cannot forward a node with children"); 49 | } 50 | this.target = target; 51 | this.modifier = modifier; 52 | this.forks = fork; 53 | return this.getThis(); 54 | } 55 | getRedirect() { 56 | return this.target; 57 | } 58 | getRedirectModifier() { 59 | return this.modifier; 60 | } 61 | isFork() { 62 | return this.forks; 63 | } 64 | } 65 | exports.default = ArgumentBuilder; 66 | -------------------------------------------------------------------------------- /dist/lib/builder/LiteralArgumentBuilder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const LiteralCommandNode_1 = __importDefault(require("../tree/LiteralCommandNode")); 7 | const ArgumentBuilder_1 = __importDefault(require("./ArgumentBuilder")); 8 | class LiteralArgumentBuilder extends ArgumentBuilder_1.default { 9 | constructor(literal) { 10 | super(); 11 | this.literal = literal; 12 | } 13 | static literal(name) { 14 | return new LiteralArgumentBuilder(name); 15 | } 16 | getThis() { 17 | return this; 18 | } 19 | getLiteral() { 20 | return this.literal; 21 | } 22 | build() { 23 | let result = new LiteralCommandNode_1.default(this.getLiteral(), this.getCommand(), this.getRequirement(), this.getRedirect(), this.getRedirectModifier(), this.isFork()); 24 | for (let arg of this.getArguments()) { 25 | result.addChild(arg); 26 | } 27 | return result; 28 | } 29 | } 30 | exports.default = LiteralArgumentBuilder; 31 | exports.literal = LiteralArgumentBuilder.literal; 32 | -------------------------------------------------------------------------------- /dist/lib/builder/RequiredArgumentBuilder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const ArgumentCommandNode_1 = __importDefault(require("../tree/ArgumentCommandNode")); 7 | const ArgumentBuilder_1 = __importDefault(require("./ArgumentBuilder")); 8 | class RequiredArgumentBuilder extends ArgumentBuilder_1.default { 9 | constructor(name, type) { 10 | super(); 11 | this.name = name; 12 | this.type = type; 13 | } 14 | static argument(name, type) { 15 | return new RequiredArgumentBuilder(name, type); 16 | } 17 | suggests(provider) { 18 | this.suggestionsProvider = provider; 19 | return this.getThis(); 20 | } 21 | getSuggestionsProvider() { 22 | return this.suggestionsProvider; 23 | } 24 | getThis() { 25 | return this; 26 | } 27 | getType() { 28 | return this.type; 29 | } 30 | getName() { 31 | return this.name; 32 | } 33 | build() { 34 | let result = new ArgumentCommandNode_1.default(this.getName(), this.getType(), this.getCommand(), this.getRequirement(), this.getRedirect(), this.getRedirectModifier(), this.isFork(), this.getSuggestionsProvider()); 35 | for (let arg of this.getArguments()) { 36 | result.addChild(arg); 37 | } 38 | return result; 39 | } 40 | } 41 | exports.default = RequiredArgumentBuilder; 42 | exports.argument = RequiredArgumentBuilder.argument; 43 | -------------------------------------------------------------------------------- /dist/lib/context/CommandContext.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const isEqual_1 = __importDefault(require("../util/isEqual")); 7 | class CommandContext { 8 | constructor(source, input, args, command, rootNode, nodes, range, child, modifier, forks) { 9 | this.source = source; 10 | this.input = input; 11 | this.args = args; 12 | this.command = command; 13 | this.rootNode = rootNode; 14 | this.nodes = nodes; 15 | this.range = range; 16 | this.child = child; 17 | this.modifier = modifier; 18 | this.forks = forks; 19 | } 20 | copyFor(source) { 21 | if (this.source === source) 22 | return this; 23 | return new CommandContext(source, this.input, this.args, this.command, this.rootNode, this.nodes, this.range, this.child, this.modifier, this.forks); 24 | } 25 | getChild() { 26 | return this.child; 27 | } 28 | getLastChild() { 29 | let result = this; 30 | while (!(result.getChild() == null)) { 31 | result = result.getChild(); 32 | } 33 | return result; 34 | } 35 | getCommand() { 36 | return this.command; 37 | } 38 | getSource() { 39 | return this.source; 40 | } 41 | getArgument(name, clazz) { 42 | const arg = this.args.get(name); 43 | if (arg == null) { 44 | throw new Error("No such argument '" + name + "' exists on this command"); 45 | } 46 | let result = arg.getResult(); 47 | if (clazz == null) { 48 | return result; 49 | } 50 | else { 51 | return clazz(result); 52 | } 53 | } 54 | equals(o) { 55 | if (this === o) 56 | return true; 57 | if (!(o instanceof CommandContext)) 58 | return false; 59 | if (!isEqual_1.default(this.args, o.args)) 60 | return false; 61 | if (!this.rootNode.equals(o.rootNode)) 62 | return false; 63 | if (this.nodes.length != o.nodes.length || !isEqual_1.default(this.nodes, o.nodes)) 64 | return false; 65 | if (!(this.command == null) ? !isEqual_1.default(this.command, o.command) : o.command != null) 66 | return false; 67 | if (!isEqual_1.default(this.source, o.source)) 68 | return false; 69 | if (!(this.child == null) ? !this.child.equals(o.child) : o.child != null) 70 | return false; 71 | return true; 72 | } 73 | getRedirectModifier() { 74 | return this.modifier; 75 | } 76 | getRange() { 77 | return this.range; 78 | } 79 | getInput() { 80 | return this.input; 81 | } 82 | getRootNode() { 83 | return this.rootNode; 84 | } 85 | getNodes() { 86 | return this.nodes; 87 | } 88 | hasNodes() { 89 | return this.nodes.length >= 0; 90 | } 91 | isForked() { 92 | return this.forks; 93 | } 94 | } 95 | exports.default = CommandContext; 96 | -------------------------------------------------------------------------------- /dist/lib/context/CommandContextBuilder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const StringRange_1 = __importDefault(require("./StringRange")); 7 | const CommandContext_1 = __importDefault(require("./CommandContext")); 8 | const SuggestionContext_1 = __importDefault(require("./SuggestionContext")); 9 | const ParsedCommandNode_1 = __importDefault(require("./ParsedCommandNode")); 10 | class CommandContextBuilder { 11 | constructor(dispatcher, source, rootNode, start) { 12 | this.args = new Map(); 13 | this.nodes = []; 14 | this.modifier = null; 15 | this.rootNode = rootNode; 16 | this.dispatcher = dispatcher; 17 | this.source = source; 18 | this.range = StringRange_1.default.at(start); 19 | } 20 | withSource(source) { 21 | this.source = source; 22 | return this; 23 | } 24 | getSource() { 25 | return this.source; 26 | } 27 | getRootNode() { 28 | return this.rootNode; 29 | } 30 | withArgument(name, argument) { 31 | this.args.set(name, argument); 32 | return this; 33 | } 34 | getArguments() { 35 | return this.args; 36 | } 37 | withCommand(command) { 38 | this.command = command; 39 | return this; 40 | } 41 | withNode(node, range) { 42 | this.nodes.push(new ParsedCommandNode_1.default(node, range)); 43 | this.range = StringRange_1.default.encompassing(this.range, range); 44 | this.modifier = node.getRedirectModifier(); 45 | this.forks = node.isFork(); 46 | return this; 47 | } 48 | copy() { 49 | const copy = new CommandContextBuilder(this.dispatcher, this.source, this.rootNode, this.range.getStart()); 50 | copy.command = this.command; 51 | copy.args = new Map([...copy.args, ...this.args]); 52 | copy.nodes.push(...this.nodes); 53 | copy.child = this.child; 54 | copy.range = this.range; 55 | copy.forks = this.forks; 56 | return copy; 57 | } 58 | withChild(child) { 59 | this.child = child; 60 | return this; 61 | } 62 | getChild() { 63 | return this.child; 64 | } 65 | getLastChild() { 66 | let result = this; 67 | while (result.getChild() != null) { 68 | result = result.getChild(); 69 | } 70 | return result; 71 | } 72 | getCommand() { 73 | return this.command; 74 | } 75 | getNodes() { 76 | return this.nodes; 77 | } 78 | build(input) { 79 | return new CommandContext_1.default(this.source, input, this.args, this.command, this.rootNode, this.nodes, this.range, this.child == null ? null : this.child.build(input), this.modifier, this.forks); 80 | } 81 | getDispatcher() { 82 | return this.dispatcher; 83 | } 84 | getRange() { 85 | return this.range; 86 | } 87 | findSuggestionContext(cursor) { 88 | if ((this.range.getStart() <= cursor)) { 89 | if ((this.range.getEnd() < cursor)) { 90 | if ((this.child != null)) { 91 | return this.child.findSuggestionContext(cursor); 92 | } 93 | else if (this.nodes.length > 0) { 94 | let last = this.nodes[this.nodes.length - 1]; 95 | return new SuggestionContext_1.default(last.getNode(), last.getRange().getEnd() + 1); 96 | } 97 | else { 98 | return new SuggestionContext_1.default(this.rootNode, this.range.getStart()); 99 | } 100 | } 101 | else { 102 | let prev = this.rootNode; 103 | for (let node of this.nodes) { 104 | let nodeRange = node.getRange(); 105 | if (nodeRange.getStart() <= cursor && cursor <= nodeRange.getEnd()) { 106 | return new SuggestionContext_1.default(prev, nodeRange.getStart()); 107 | } 108 | prev = node.getNode(); 109 | } 110 | if ((prev == null)) { 111 | throw new Error("Can't find node before cursor"); 112 | } 113 | return new SuggestionContext_1.default(prev, this.range.getStart()); 114 | } 115 | } 116 | throw new Error("Can't find node before cursor"); 117 | } 118 | } 119 | exports.default = CommandContextBuilder; 120 | -------------------------------------------------------------------------------- /dist/lib/context/ParsedArgument.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const StringRange_1 = __importDefault(require("./StringRange")); 7 | class ParsedArgument { 8 | constructor(start, end, result) { 9 | this.range = StringRange_1.default.between(start, end); 10 | this.result = result; 11 | } 12 | getRange() { 13 | return this.range; 14 | } 15 | getResult() { 16 | return this.result; 17 | } 18 | equals(o) { 19 | if (this === o) 20 | return true; 21 | if (!(o instanceof ParsedArgument)) 22 | return false; 23 | return this.range.equals(o.range) && this.result === o.result; 24 | } 25 | } 26 | exports.default = ParsedArgument; 27 | -------------------------------------------------------------------------------- /dist/lib/context/ParsedCommandNode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class ParsedCommandNode { 4 | constructor(node, range) { 5 | this.node = node; 6 | this.range = range; 7 | } 8 | getNode() { 9 | return this.node; 10 | } 11 | getRange() { 12 | return this.range; 13 | } 14 | toString() { 15 | return this.node + "@" + this.range; 16 | } 17 | equals(o) { 18 | if (this === o) 19 | return true; 20 | if (o == null || !(o instanceof ParsedCommandNode)) { 21 | return false; 22 | } 23 | return this.node.equals(o.node) && this.range.equals(o.range); 24 | } 25 | } 26 | exports.default = ParsedCommandNode; 27 | -------------------------------------------------------------------------------- /dist/lib/context/StringRange.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class StringRange { 4 | constructor(start, end) { 5 | this.start = start; 6 | this.end = end; 7 | } 8 | static at(pos) { 9 | return new StringRange(pos, pos); 10 | } 11 | static between(start, end) { 12 | return new StringRange(start, end); 13 | } 14 | static encompassing(a, b) { 15 | return new StringRange(Math.min(a.getStart(), b.getStart()), Math.max(a.getEnd(), b.getEnd())); 16 | } 17 | getStart() { 18 | return this.start; 19 | } 20 | getEnd() { 21 | return this.end; 22 | } 23 | get(str) { 24 | if (typeof str === "string") 25 | return str.substring(this.start, this.end); 26 | else 27 | return str.getString().substring(this.start, this.end); 28 | } 29 | isEmpty() { 30 | return this.start === this.end; 31 | } 32 | getLength() { 33 | return this.end - this.start; 34 | } 35 | equals(o) { 36 | if (this === o) 37 | return true; 38 | if (!(o instanceof StringRange)) 39 | return false; 40 | return this.start === o.start && this.end == o.end; 41 | } 42 | toString() { 43 | return "StringRange{" + "start=" + this.start + ", end=" + this.end + '}'; 44 | } 45 | } 46 | exports.default = StringRange; 47 | -------------------------------------------------------------------------------- /dist/lib/context/SuggestionContext.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class SuggestionContext { 4 | constructor(parent, startPos) { 5 | this.parent = parent; 6 | this.startPos = startPos; 7 | } 8 | } 9 | exports.default = SuggestionContext; 10 | -------------------------------------------------------------------------------- /dist/lib/exceptions/BuiltInExceptionProvider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/exceptions/BuiltInExceptions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const LiteralMessage_1 = __importDefault(require("../LiteralMessage")); 7 | const SimpleCommandExceptionType_1 = __importDefault(require("./SimpleCommandExceptionType")); 8 | const DynamicCommandExceptionType_1 = __importDefault(require("./DynamicCommandExceptionType")); 9 | class BuiltInExceptions { 10 | floatTooLow() { 11 | return BuiltInExceptions.FLOAT_TOO_SMALL; 12 | } 13 | floatTooHigh() { 14 | return BuiltInExceptions.FLOAT_TOO_BIG; 15 | } 16 | integerTooLow() { 17 | return BuiltInExceptions.INTEGER_TOO_SMALL; 18 | } 19 | integerTooHigh() { 20 | return BuiltInExceptions.INTEGER_TOO_BIG; 21 | } 22 | literalIncorrect() { 23 | return BuiltInExceptions.LITERAL_INCORRECT; 24 | } 25 | readerExpectedStartOfQuote() { 26 | return BuiltInExceptions.READER_EXPECTED_START_OF_QUOTE; 27 | } 28 | readerExpectedEndOfQuote() { 29 | return BuiltInExceptions.READER_EXPECTED_END_OF_QUOTE; 30 | } 31 | readerInvalidEscape() { 32 | return BuiltInExceptions.READER_INVALID_ESCAPE; 33 | } 34 | readerInvalidBool() { 35 | return BuiltInExceptions.READER_INVALID_BOOL; 36 | } 37 | readerInvalidInt() { 38 | return BuiltInExceptions.READER_INVALID_INT; 39 | } 40 | readerExpectedInt() { 41 | return BuiltInExceptions.READER_EXPECTED_INT; 42 | } 43 | readerInvalidFloat() { 44 | return BuiltInExceptions.READER_INVALID_FLOAT; 45 | } 46 | readerExpectedFloat() { 47 | return BuiltInExceptions.READER_EXPECTED_FLOAT; 48 | } 49 | readerExpectedBool() { 50 | return BuiltInExceptions.READER_EXPECTED_BOOL; 51 | } 52 | readerExpectedSymbol() { 53 | return BuiltInExceptions.READER_EXPECTED_SYMBOL; 54 | } 55 | dispatcherUnknownCommand() { 56 | return BuiltInExceptions.DISPATCHER_UNKNOWN_COMMAND; 57 | } 58 | dispatcherUnknownArgument() { 59 | return BuiltInExceptions.DISPATCHER_UNKNOWN_ARGUMENT; 60 | } 61 | dispatcherExpectedArgumentSeparator() { 62 | return BuiltInExceptions.DISPATCHER_EXPECTED_ARGUMENT_SEPARATOR; 63 | } 64 | dispatcherParseException() { 65 | return BuiltInExceptions.DISPATCHER_PARSE_EXCEPTION; 66 | } 67 | } 68 | BuiltInExceptions.FLOAT_TOO_SMALL = new DynamicCommandExceptionType_1.default((found, min) => new LiteralMessage_1.default("Float must not be less than " + min + ", found " + found)); 69 | BuiltInExceptions.FLOAT_TOO_BIG = new DynamicCommandExceptionType_1.default((found, max) => new LiteralMessage_1.default("Float must not be more than " + max + ", found " + found)); 70 | BuiltInExceptions.INTEGER_TOO_SMALL = new DynamicCommandExceptionType_1.default((found, min) => new LiteralMessage_1.default("Integer must not be less than " + min + ", found " + found)); 71 | BuiltInExceptions.INTEGER_TOO_BIG = new DynamicCommandExceptionType_1.default((found, max) => new LiteralMessage_1.default("Integer must not be more than " + max + ", found " + found)); 72 | BuiltInExceptions.LITERAL_INCORRECT = new DynamicCommandExceptionType_1.default(expected => new LiteralMessage_1.default("Expected literal " + expected)); 73 | BuiltInExceptions.READER_EXPECTED_START_OF_QUOTE = new SimpleCommandExceptionType_1.default(new LiteralMessage_1.default("Expected quote to start a string")); 74 | BuiltInExceptions.READER_EXPECTED_END_OF_QUOTE = new SimpleCommandExceptionType_1.default(new LiteralMessage_1.default("Unclosed quoted string")); 75 | BuiltInExceptions.READER_INVALID_ESCAPE = new DynamicCommandExceptionType_1.default(character => new LiteralMessage_1.default("Invalid escape sequence '" + character + "' in quoted string")); 76 | BuiltInExceptions.READER_INVALID_BOOL = new DynamicCommandExceptionType_1.default(value => new LiteralMessage_1.default("Invalid bool, expected true or false but found '" + value + "'")); 77 | BuiltInExceptions.READER_INVALID_INT = new DynamicCommandExceptionType_1.default(value => new LiteralMessage_1.default("Invalid integer '" + value + "'")); 78 | BuiltInExceptions.READER_EXPECTED_INT = new SimpleCommandExceptionType_1.default(new LiteralMessage_1.default("Expected integer")); 79 | BuiltInExceptions.READER_INVALID_FLOAT = new DynamicCommandExceptionType_1.default(value => new LiteralMessage_1.default("Invalid float '" + value + "'")); 80 | BuiltInExceptions.READER_EXPECTED_FLOAT = new SimpleCommandExceptionType_1.default(new LiteralMessage_1.default("Expected float")); 81 | BuiltInExceptions.READER_EXPECTED_BOOL = new SimpleCommandExceptionType_1.default(new LiteralMessage_1.default("Expected bool")); 82 | BuiltInExceptions.READER_EXPECTED_SYMBOL = new DynamicCommandExceptionType_1.default(symbol => new LiteralMessage_1.default("Expected '" + symbol + "'")); 83 | BuiltInExceptions.DISPATCHER_UNKNOWN_COMMAND = new SimpleCommandExceptionType_1.default(new LiteralMessage_1.default("Unknown command")); 84 | BuiltInExceptions.DISPATCHER_UNKNOWN_ARGUMENT = new SimpleCommandExceptionType_1.default(new LiteralMessage_1.default("Incorrect argument for command")); 85 | BuiltInExceptions.DISPATCHER_EXPECTED_ARGUMENT_SEPARATOR = new SimpleCommandExceptionType_1.default(new LiteralMessage_1.default("Expected whitespace to end one argument, but found trailing data")); 86 | BuiltInExceptions.DISPATCHER_PARSE_EXCEPTION = new DynamicCommandExceptionType_1.default(message => new LiteralMessage_1.default(("Could not parse command: " + message))); 87 | exports.default = BuiltInExceptions; 88 | -------------------------------------------------------------------------------- /dist/lib/exceptions/CommandExceptionType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/exceptions/CommandSyntaxException.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const BuiltInExceptions_1 = __importDefault(require("./BuiltInExceptions")); 7 | class CommandSyntaxException extends Error { 8 | constructor(type, message, input = null, cursor = -1) { 9 | super(message.getString()); 10 | Error.captureStackTrace(this, CommandSyntaxException); 11 | this.type = type; 12 | this.__message = message; 13 | this.input = input; 14 | this.cursor = cursor; 15 | this.message = this.getMessage(); 16 | } 17 | getMessage() { 18 | let message = this.__message.getString(); 19 | let context = this.getContext(); 20 | if (context != null) { 21 | message += " at position " + this.cursor + ": " + context; 22 | } 23 | return message; 24 | } 25 | getRawMessage() { 26 | return this.__message; 27 | } 28 | getContext() { 29 | if (this.input == null || this.cursor < 0) { 30 | return null; 31 | } 32 | let builder = ""; 33 | let cursor = Math.min(this.input.length, this.cursor); 34 | if (cursor > CommandSyntaxException.CONTEXT_AMOUNT) { 35 | builder += "..."; 36 | } 37 | builder += this.input.substring(Math.max(0, cursor - CommandSyntaxException.CONTEXT_AMOUNT), cursor); 38 | builder += "<--[HERE]"; 39 | return builder; 40 | } 41 | getType() { 42 | return this.type; 43 | } 44 | getInput() { 45 | return this.input; 46 | } 47 | getCursor() { 48 | return this.cursor; 49 | } 50 | toString() { 51 | return this.message; 52 | } 53 | } 54 | CommandSyntaxException.CONTEXT_AMOUNT = 10; 55 | CommandSyntaxException.BUILT_IN_EXCEPTIONS = new BuiltInExceptions_1.default(); 56 | exports.default = CommandSyntaxException; 57 | -------------------------------------------------------------------------------- /dist/lib/exceptions/Dynamic2CommandExceptionType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const CommandSyntaxException_1 = __importDefault(require("./CommandSyntaxException")); 7 | class Dynamic2CommandExceptionType { 8 | constructor(fn) { 9 | this.fn = fn; 10 | Error.captureStackTrace(this, Dynamic2CommandExceptionType); 11 | } 12 | create(a, b) { 13 | return new CommandSyntaxException_1.default(this, this.fn(a, b)); 14 | } 15 | createWithContext(reader, a, b) { 16 | return new CommandSyntaxException_1.default(this, this.fn(a, b), reader.getString(), reader.getCursor()); 17 | } 18 | } 19 | exports.default = Dynamic2CommandExceptionType; 20 | -------------------------------------------------------------------------------- /dist/lib/exceptions/DynamicCommandExceptionType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const CommandSyntaxException_1 = __importDefault(require("./CommandSyntaxException")); 7 | class DynamicCommandExceptionType { 8 | constructor(fn) { 9 | this.fn = fn; 10 | Error.captureStackTrace(this, DynamicCommandExceptionType); 11 | } 12 | create(...args) { 13 | return new CommandSyntaxException_1.default(this, this.fn(...args)); 14 | } 15 | createWithContext(reader, ...args) { 16 | return new CommandSyntaxException_1.default(this, this.fn(...args), reader.getString(), reader.getCursor()); 17 | } 18 | } 19 | exports.default = DynamicCommandExceptionType; 20 | -------------------------------------------------------------------------------- /dist/lib/exceptions/SimpleCommandExceptionType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const CommandSyntaxException_1 = __importDefault(require("./CommandSyntaxException")); 7 | class SimpleCommandExceptionType { 8 | constructor(message) { 9 | this.message = message; 10 | Error.captureStackTrace(this, SimpleCommandExceptionType); 11 | } 12 | create() { 13 | return new CommandSyntaxException_1.default(this, this.message); 14 | } 15 | createWithContext(reader) { 16 | return new CommandSyntaxException_1.default(this, this.message, reader.getString(), reader.getCursor()); 17 | } 18 | toString() { 19 | return this.message.getString(); 20 | } 21 | } 22 | exports.default = SimpleCommandExceptionType; 23 | -------------------------------------------------------------------------------- /dist/lib/suggestion/IntegerSuggestion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const Suggestion_1 = __importDefault(require("./Suggestion")); 7 | class IntegerSuggestion extends Suggestion_1.default { 8 | constructor(range, value, tooltip = null) { 9 | super(range, value.toString(), tooltip); 10 | this.value = value; 11 | } 12 | getValue() { 13 | return this.value; 14 | } 15 | equals(o) { 16 | if (this === o) 17 | return true; 18 | if (!(o instanceof IntegerSuggestion)) 19 | return false; 20 | return this.value == o.value && super.equals(o); 21 | } 22 | toString() { 23 | return "IntegerSuggestion{" + 24 | "value=" + this.value + 25 | ", range=" + this.getRange() + 26 | ", text='" + this.getText() + '\'' + 27 | ", tooltip='" + this.getTooltip() + '\'' + 28 | '}'; 29 | } 30 | compareTo(o) { 31 | if (o instanceof IntegerSuggestion) { 32 | return this.value < o.value ? 1 : -1; 33 | } 34 | return super.compareTo(o); 35 | } 36 | compareToIgnoreCase(b) { 37 | return this.compareTo(b); 38 | } 39 | } 40 | exports.default = IntegerSuggestion; 41 | -------------------------------------------------------------------------------- /dist/lib/suggestion/Suggestion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const isEqual_1 = __importDefault(require("../util/isEqual")); 7 | class Suggestion { 8 | constructor(range, text, tooltip = null) { 9 | this.range = range; 10 | this.text = text; 11 | this.tooltip = tooltip; 12 | } 13 | getRange() { 14 | return this.range; 15 | } 16 | getText() { 17 | return this.text; 18 | } 19 | getTooltip() { 20 | return this.tooltip; 21 | } 22 | apply(input) { 23 | if (this.range.getStart() === 0 && this.range.getEnd() == input.length) { 24 | return this.text; 25 | } 26 | let result = ""; 27 | if (this.range.getStart() > 0) { 28 | result += input.substring(0, this.range.getStart()); 29 | } 30 | result += this.text; 31 | if (this.range.getEnd() < input.length) { 32 | result += input.substring(this.range.getEnd()); 33 | } 34 | return result; 35 | } 36 | equals(o) { 37 | if (this === o) 38 | return true; 39 | if (!(o instanceof Suggestion)) 40 | return false; 41 | return isEqual_1.default(this.range, o.range) && (this.text === o.text) && isEqual_1.default(this.tooltip, o.tooltip); 42 | } 43 | toString() { 44 | return "Suggestion{" + 45 | "range=" + this.range + 46 | ", text='" + this.text + '\'' + 47 | ", tooltip='" + this.tooltip + '\'' + 48 | '}'; 49 | } 50 | compareTo(o) { 51 | return this.text < o.text ? 1 : -1; 52 | } 53 | compareToIgnoreCase(b) { 54 | return this.text.toLowerCase() < b.text.toLowerCase() ? 1 : -1; 55 | } 56 | expand(command, range) { 57 | if (range.equals(this.range)) { 58 | return this; 59 | } 60 | let result = ""; 61 | if (range.getStart() < this.range.getStart()) { 62 | result += command.substring(range.getStart(), this.range.getStart()); 63 | } 64 | result += this.text; 65 | if (range.getEnd() > this.range.getEnd()) { 66 | result += command.substring(this.range.getEnd(), range.getEnd()); 67 | } 68 | return new Suggestion(range, result, this.tooltip); 69 | } 70 | } 71 | exports.default = Suggestion; 72 | -------------------------------------------------------------------------------- /dist/lib/suggestion/SuggestionProvider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/lib/suggestion/Suggestions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const isEqual_1 = __importDefault(require("../util/isEqual")); 7 | const StringRange_1 = __importDefault(require("../context/StringRange")); 8 | class Suggestions { 9 | constructor(range, suggestions) { 10 | this.range = range; 11 | this.suggestions = suggestions; 12 | } 13 | getRange() { 14 | return this.range; 15 | } 16 | getList() { 17 | return this.suggestions; 18 | } 19 | isEmpty() { 20 | return this.suggestions.length === 0; 21 | } 22 | equals(o) { 23 | if (this === o) 24 | return true; 25 | if (!(o instanceof Suggestions)) 26 | return false; 27 | return this.range.equals(o.range) && isEqual_1.default(this.suggestions, o.suggestions); 28 | } 29 | toString() { 30 | return "Suggestions{" + 31 | "range=" + this.range + 32 | ", suggestions=" + this.suggestions + '}'; 33 | } 34 | static empty() { 35 | return Promise.resolve(this.EMPTY); 36 | } 37 | static merge(command, input) { 38 | if (input.length === 0) { 39 | return this.EMPTY; 40 | } 41 | else if (input.length === 1) { 42 | return input[0]; 43 | } 44 | const texts = []; 45 | for (let suggestions of input) { 46 | texts.push(...suggestions.getList()); 47 | } 48 | return Suggestions.create(command, texts); 49 | } 50 | static create(command, suggestions) { 51 | if (suggestions.length === 0) { 52 | return this.EMPTY; 53 | } 54 | let start = Infinity; 55 | let end = -Infinity; 56 | for (let suggestion of suggestions) { 57 | start = Math.min(suggestion.getRange().getStart(), start); 58 | end = Math.max(suggestion.getRange().getEnd(), end); 59 | } 60 | let range = new StringRange_1.default(start, end); 61 | const texts = []; 62 | for (let suggestion of suggestions) { 63 | texts.push(suggestion.expand(command, range)); 64 | } 65 | const sorted = texts.sort((a, b) => a.compareToIgnoreCase(b)); 66 | return new Suggestions(range, sorted); 67 | } 68 | } 69 | Suggestions.EMPTY = new Suggestions(StringRange_1.default.at(0), []); 70 | exports.default = Suggestions; 71 | -------------------------------------------------------------------------------- /dist/lib/suggestion/SuggestionsBuilder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const StringRange_1 = __importDefault(require("../context/StringRange")); 7 | const Suggestion_1 = __importDefault(require("./Suggestion")); 8 | const Suggestions_1 = __importDefault(require("./Suggestions")); 9 | const IntegerSuggestion_1 = __importDefault(require("./IntegerSuggestion")); 10 | class SuggestionsBuilder { 11 | constructor(input, start) { 12 | this.result = []; 13 | this.input = input; 14 | this.start = start; 15 | this.remaining = input.substring(start); 16 | } 17 | getInput() { 18 | return this.input; 19 | } 20 | getStart() { 21 | return this.start; 22 | } 23 | getRemaining() { 24 | return this.remaining; 25 | } 26 | build() { 27 | return Suggestions_1.default.create(this.input, this.result); 28 | } 29 | buildPromise() { 30 | return Promise.resolve(this.build()); 31 | } 32 | suggest(text, tooltip = null) { 33 | if (typeof text === "number") { 34 | this.result.push(new IntegerSuggestion_1.default(StringRange_1.default.between(this.start, this.input.length), text, tooltip)); 35 | return this; 36 | } 37 | if (text === this.remaining) 38 | return this; 39 | this.result.push(new Suggestion_1.default(StringRange_1.default.between(this.start, this.input.length), text, tooltip)); 40 | return this; 41 | } 42 | add(other) { 43 | this.result.push(...other.result); 44 | return this; 45 | } 46 | createOffset(start) { 47 | return new SuggestionsBuilder(this.input, this.start); 48 | } 49 | restart() { 50 | return new SuggestionsBuilder(this.input, this.start); 51 | } 52 | } 53 | exports.default = SuggestionsBuilder; 54 | -------------------------------------------------------------------------------- /dist/lib/tree/ArgumentCommandNode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const isEqual_1 = __importDefault(require("../util/isEqual")); 7 | const StringReader_1 = __importDefault(require("../StringReader")); 8 | const RequiredArgumentBuilder_1 = __importDefault(require("../builder/RequiredArgumentBuilder")); 9 | const ParsedArgument_1 = __importDefault(require("../context/ParsedArgument")); 10 | const Suggestions_1 = __importDefault(require("../suggestion/Suggestions")); 11 | const CommandNode_1 = __importDefault(require("./CommandNode")); 12 | const USAGE_ARGUMENT_OPEN = "<"; 13 | const USAGE_ARGUMENT_CLOSE = ">"; 14 | class ArgumentCommandNode extends CommandNode_1.default { 15 | constructor(name, type, command, requirement, redirect, modifier, forks, customSuggestions) { 16 | super(command, requirement, redirect, modifier, forks); 17 | this.name = name; 18 | this.type = type; 19 | this.customSuggestions = customSuggestions; 20 | } 21 | getNodeType() { 22 | return "argument"; 23 | } 24 | getType() { 25 | return this.type; 26 | } 27 | getName() { 28 | return this.name; 29 | } 30 | getUsageText() { 31 | return USAGE_ARGUMENT_OPEN + this.name + USAGE_ARGUMENT_CLOSE; 32 | } 33 | getCustomSuggestions() { 34 | return this.customSuggestions; 35 | } 36 | parse(reader, contextBuilder) { 37 | let start = reader.getCursor(); 38 | let result = this.type.parse(reader); 39 | let parsed = new ParsedArgument_1.default(start, reader.getCursor(), result); 40 | contextBuilder.withArgument(this.name, parsed); 41 | contextBuilder.withNode(this, parsed.getRange()); 42 | } 43 | listSuggestions(context, builder) { 44 | if (this.customSuggestions == null) { 45 | if (typeof this.type.listSuggestions === "function") 46 | return this.type.listSuggestions(context, builder); 47 | else 48 | return Suggestions_1.default.empty(); 49 | } 50 | else { 51 | return this.customSuggestions.getSuggestions(context, builder); 52 | } 53 | } 54 | createBuilder() { 55 | let builder = RequiredArgumentBuilder_1.default.argument(this.name, this.type); 56 | builder.requires(this.getRequirement()); 57 | builder.forward(this.getRedirect(), this.getRedirectModifier(), this.isFork()); 58 | builder.suggests(this.customSuggestions); 59 | if (this.getCommand() != null) { 60 | builder.executes(this.getCommand()); 61 | } 62 | return builder; 63 | } 64 | isValidInput(input) { 65 | try { 66 | let reader = new StringReader_1.default(input); 67 | this.type.parse(reader); 68 | return !reader.canRead() || reader.peek() == ' '; 69 | } 70 | catch (ignored) { 71 | } 72 | return false; 73 | } 74 | equals(o) { 75 | if (this === o) 76 | return true; 77 | if (!(o instanceof ArgumentCommandNode)) 78 | return false; 79 | if (!(this.name === o.name)) 80 | return false; 81 | if (!isEqual_1.default(this.type, o.type)) 82 | return false; 83 | return super.equals(o); 84 | } 85 | getSortedKey() { 86 | return this.name; 87 | } 88 | getExamples() { 89 | return typeof this.type.getExamples === "function" ? this.type.getExamples() : []; 90 | } 91 | toString() { 92 | return ""; 93 | } 94 | } 95 | exports.default = ArgumentCommandNode; 96 | -------------------------------------------------------------------------------- /dist/lib/tree/CommandNode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const isEqual_1 = __importDefault(require("../util/isEqual")); 7 | class CommandNode { 8 | constructor(command, requirement, redirect, modifier, forks) { 9 | this.children = new Map(); 10 | this.literals = new Map(); 11 | this.args = new Map(); 12 | this.command = command; 13 | this.requirement = requirement || (() => true); 14 | this.redirect = redirect; 15 | this.modifier = modifier; 16 | this.forks = forks; 17 | } 18 | getCommand() { 19 | return this.command; 20 | } 21 | getChildren() { 22 | return this.children.values(); 23 | } 24 | getChildrenCount() { 25 | return this.children.size; 26 | } 27 | getChild(name) { 28 | return this.children.get(name); 29 | } 30 | getRedirect() { 31 | return this.redirect; 32 | } 33 | getRedirectModifier() { 34 | return this.modifier; 35 | } 36 | canUse(source) { 37 | return this.requirement(source); 38 | } 39 | addChild(node) { 40 | if (node.getNodeType() === "root") { 41 | throw new Error("Cannot add a RootCommandNode as a child to any other CommandNode"); 42 | } 43 | let child = this.children.get(node.getName()); 44 | if (child != null) { 45 | // We've found something to merge onto 46 | if ((node.getCommand() != null)) { 47 | child.command = node.getCommand(); 48 | } 49 | for (let grandchild of node.getChildren()) { 50 | child.addChild(grandchild); 51 | } 52 | } 53 | else { 54 | this.children.set(node.getName(), node); 55 | if (node.getNodeType() === "literal") { 56 | this.literals.set(node.getName(), node); 57 | } 58 | else if (node.getNodeType() === "argument") { 59 | this.args.set(node.getName(), node); 60 | } 61 | } 62 | this.children = new Map([...this.children.entries()].sort((a, b) => a[1].compareTo(b[1]))); 63 | } 64 | findAmbiguities(consumer) { 65 | let matches = []; 66 | for (let child of this.children.values()) { 67 | for (let sibling of this.children.values()) { 68 | if (child === sibling) { 69 | continue; 70 | } 71 | for (let input of child.getExamples()) { 72 | if (sibling.isValidInput(input)) { 73 | matches.push(input); 74 | } 75 | } 76 | if (matches.length > 0) { 77 | consumer.ambiguous(this, child, sibling, matches); 78 | matches = []; 79 | } 80 | } 81 | child.findAmbiguities(consumer); 82 | } 83 | } 84 | equals(o) { 85 | if (this === o) 86 | return true; 87 | if (!(o instanceof CommandNode)) 88 | return false; 89 | if (this.children.size !== o.children.size) { 90 | return false; 91 | } 92 | if (!isEqual_1.default(this.children, o.children)) 93 | return false; 94 | if (this.command != null ? !isEqual_1.default(this.command, o.command) : o.command != null) 95 | return false; 96 | return true; 97 | } 98 | getRequirement() { 99 | return this.requirement; 100 | } 101 | getRelevantNodes(input) { 102 | if (this.literals.size > 0) { 103 | let cursor = input.getCursor(); 104 | while ((input.canRead() 105 | && (input.peek() != ' '))) { 106 | input.skip(); 107 | } 108 | let text = input.getString().substring(cursor, input.getCursor()); 109 | input.setCursor(cursor); 110 | let literal = this.literals.get(text); 111 | if (literal != null) { 112 | return [literal]; 113 | } 114 | else { 115 | return this.args.values(); 116 | } 117 | } 118 | else { 119 | return this.args.values(); 120 | } 121 | } 122 | compareTo(o) { 123 | if (this.getNodeType() === o.getNodeType()) { 124 | return this.getSortedKey() > o.getSortedKey() ? 1 : -1; 125 | } 126 | return (o.getNodeType() === "literal") ? 1 : -1; 127 | } 128 | isFork() { 129 | return this.forks; 130 | } 131 | } 132 | exports.default = CommandNode; 133 | -------------------------------------------------------------------------------- /dist/lib/tree/LiteralCommandNode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const CommandNode_1 = __importDefault(require("./CommandNode")); 7 | const StringReader_1 = __importDefault(require("../StringReader")); 8 | const LiteralArgumentBuilder_1 = __importDefault(require("../builder/LiteralArgumentBuilder")); 9 | const StringRange_1 = __importDefault(require("../context/StringRange")); 10 | const CommandSyntaxException_1 = __importDefault(require("../exceptions/CommandSyntaxException")); 11 | const Suggestions_1 = __importDefault(require("../suggestion/Suggestions")); 12 | class LiteralCommandNode extends CommandNode_1.default { 13 | constructor(literal, command, requirement, redirect, modifier, forks) { 14 | super(command, requirement, redirect, modifier, forks); 15 | this.literal = literal; 16 | } 17 | getNodeType() { 18 | return "literal"; 19 | } 20 | getLiteral() { 21 | return this.literal; 22 | } 23 | getName() { 24 | return this.literal; 25 | } 26 | parse(reader, contextBuilder) { 27 | let start = reader.getCursor(); 28 | let end = this.__parse(reader); 29 | if (end > -1) { 30 | contextBuilder.withNode(this, StringRange_1.default.between(start, end)); 31 | return; 32 | } 33 | throw CommandSyntaxException_1.default.BUILT_IN_EXCEPTIONS.literalIncorrect().createWithContext(reader, this.literal); 34 | } 35 | __parse(reader) { 36 | let start = reader.getCursor(); 37 | if (reader.canRead(this.literal.length)) { 38 | let end = start + this.literal.length; 39 | if (reader.getString().substring(start, end) === this.literal) { 40 | reader.setCursor(end); 41 | if (!reader.canRead() || reader.peek() == ' ') { 42 | return end; 43 | } 44 | else { 45 | reader.setCursor(start); 46 | } 47 | } 48 | } 49 | return -1; 50 | } 51 | listSuggestions(context, builder) { 52 | if (this.literal.toLowerCase().startsWith(builder.getRemaining().toLowerCase())) { 53 | return builder.suggest(this.literal).buildPromise(); 54 | } 55 | else { 56 | return Suggestions_1.default.empty(); 57 | } 58 | } 59 | isValidInput(input) { 60 | return this.__parse(new StringReader_1.default(input)) > -1; 61 | } 62 | equals(o) { 63 | if (this === o) 64 | return true; 65 | if (!(o instanceof LiteralCommandNode)) 66 | return false; 67 | if (!(this.literal === o.literal)) 68 | return false; 69 | return super.equals(o); 70 | } 71 | getUsageText() { 72 | return this.literal; 73 | } 74 | createBuilder() { 75 | let builder = LiteralArgumentBuilder_1.default.literal(this.literal); 76 | builder.requires(this.getRequirement()); 77 | builder.forward(this.getRedirect(), this.getRedirectModifier(), this.isFork()); 78 | if (this.getCommand() != null) 79 | builder.executes(this.getCommand()); 80 | return builder; 81 | } 82 | getSortedKey() { 83 | return this.literal; 84 | } 85 | getExamples() { 86 | return [this.literal]; 87 | } 88 | toString() { 89 | return ""; 90 | } 91 | } 92 | exports.default = LiteralCommandNode; 93 | -------------------------------------------------------------------------------- /dist/lib/tree/RootCommandNode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const CommandNode_1 = __importDefault(require("./CommandNode")); 7 | const Suggestions_1 = __importDefault(require("../suggestion/Suggestions")); 8 | class RootCommandNode extends CommandNode_1.default { 9 | constructor() { 10 | super(null, s => true, null, (s) => s.getSource(), false); 11 | } 12 | getNodeType() { 13 | return "root"; 14 | } 15 | getName() { 16 | return ""; 17 | } 18 | getUsageText() { 19 | return ""; 20 | } 21 | parse(reader, contextBuilder) { 22 | } 23 | listSuggestions(context, builder) { 24 | return Suggestions_1.default.empty(); 25 | } 26 | isValidInput(input) { 27 | return false; 28 | } 29 | equals(o) { 30 | if (this === o) 31 | return true; 32 | if (!(o instanceof RootCommandNode)) 33 | return false; 34 | return super.equals(o); 35 | } 36 | createBuilder() { 37 | throw new Error("Cannot convert root into a builder"); 38 | } 39 | getSortedKey() { 40 | return ""; 41 | } 42 | getExamples() { 43 | return []; 44 | } 45 | toString() { 46 | return ""; 47 | } 48 | } 49 | exports.default = RootCommandNode; 50 | -------------------------------------------------------------------------------- /dist/lib/util/isEqual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function isEqual(a, b) { 4 | if (a === b) 5 | return true; 6 | if (typeof a != typeof b) 7 | return false; 8 | if (!(a instanceof Object)) 9 | return false; 10 | if (typeof a === "function") 11 | return a.toString() === b.toString(); 12 | if (a.constructor !== b.constructor) 13 | return false; 14 | if (a instanceof Map) 15 | return isMapEqual(a, b); 16 | if (a instanceof Set) 17 | return isArrayEqual([...a], [...b]); 18 | if (a instanceof Array) 19 | return isArrayEqual(a, b); 20 | if (typeof a === "object") 21 | return isObjectEqual(a, b); 22 | return false; 23 | } 24 | exports.default = isEqual; 25 | function isMapEqual(a, b) { 26 | if (a.size != b.size) 27 | return false; 28 | for (let [key, val] of a) { 29 | const testVal = b.get(key); 30 | if (!isEqual(testVal, val)) 31 | return false; 32 | if (testVal === undefined && !b.has(key)) 33 | return false; 34 | } 35 | return true; 36 | } 37 | function isArrayEqual(a, b) { 38 | if (a.length != b.length) 39 | return false; 40 | for (let i = 0; i < a.length; i++) 41 | if (!isEqual(a[i], b[i])) 42 | return false; 43 | return true; 44 | } 45 | function isObjectEqual(a, b) { 46 | const aKeys = Object.keys(a); 47 | const bKeys = Object.keys(b); 48 | if (aKeys.length != bKeys.length) 49 | return false; 50 | if (!aKeys.every(key => b.hasOwnProperty(key))) 51 | return false; 52 | return aKeys.every((key) => { 53 | return isEqual(a[key], b[key]); 54 | }); 55 | } 56 | ; 57 | -------------------------------------------------------------------------------- /example/fill.js: -------------------------------------------------------------------------------- 1 | const { CommandDispatcher, literal, argument, string, Suggestions } = require("../dist") 2 | 3 | class BlockPos { 4 | constructor(x = 0, y = 0, z = 0) { 5 | this.x = x; this.y = y; this.z = z; 6 | } 7 | parse(reader) { 8 | this.x = reader.readInt(); 9 | reader.skip(); 10 | this.y = reader.readInt(); 11 | reader.skip(); 12 | this.z = reader.readInt(); 13 | return this; 14 | } 15 | listSuggestions(context, builder) { 16 | return Suggestions.empty(); 17 | } 18 | getExamples() { 19 | return [ 20 | "1 2 3" 21 | ] 22 | } 23 | } 24 | 25 | const dispatcher = new CommandDispatcher(); 26 | 27 | dispatcher.register( 28 | literal("fill").then( 29 | argument("pos1", new BlockPos()).then( 30 | argument("pos2", new BlockPos()).then( 31 | argument("block", string()).executes(context => { 32 | console.log(context.getArgument("pos1", BlockPos)) 33 | console.log(context.getArgument("pos2", BlockPos)) 34 | console.log(context.getArgument("block", /*String*/ 3)) 35 | return 0; 36 | }) 37 | ) 38 | ) 39 | ) 40 | ) 41 | 42 | const parsedCommand = dispatcher.parse("fill 3 4 5 10 11 12 air", {}) 43 | try { 44 | dispatcher.execute(parsedCommand); 45 | } catch (ex) { 46 | console.error(ex.getMessage()); 47 | } -------------------------------------------------------------------------------- /example/fill.ts: -------------------------------------------------------------------------------- 1 | import { CommandDispatcher, literal, argument, string, ArgumentType, StringReader, CommandContext, SuggestionsBuilder, Suggestions } from "../dist" 2 | 3 | class BlockPos implements ArgumentType { 4 | 5 | public x: number; 6 | public y: number; 7 | public z: number; 8 | constructor(x: number = 0, y: number = 0, z: number = 0) { 9 | this.x = x; this.y = y; this.z = z; 10 | } 11 | parse(reader: StringReader) { 12 | this.x = reader.readInt(); 13 | reader.skip(); 14 | this.y = reader.readInt(); 15 | reader.skip(); 16 | this.z = reader.readInt(); 17 | return this; 18 | } 19 | 20 | getExamples() { 21 | return [ 22 | "1 2 3" 23 | ] 24 | } 25 | } 26 | 27 | const dispatcher = new CommandDispatcher(); 28 | 29 | dispatcher.register( 30 | literal("fill").then( 31 | argument("pos1", new BlockPos()).then( 32 | argument("pos2", new BlockPos()).then( 33 | argument("block", string()).executes(context => { 34 | console.log(context.getArgument("pos1", BlockPos)) 35 | console.log(context.getArgument("pos2", BlockPos)) 36 | console.log(context.getArgument("block", String)) 37 | return 0; 38 | }).build() 39 | ) 40 | ) 41 | ) 42 | ) 43 | 44 | const parsedCommand = dispatcher.parse("fill 3 4 5 10 11 12 air", {}) 45 | 46 | try { 47 | dispatcher.execute(parsedCommand); 48 | } catch (ex) { 49 | console.error(ex.getMessage()); 50 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index') -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-brigadier", 3 | "version": "0.0.12", 4 | "description": "A javascript port of Mojangs Brigadier library", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "homepage": "http://github.com/remtori/brigadier", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/remtori/brigadier.git" 11 | }, 12 | "keywords": [ 13 | "command" 14 | ], 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "@types/chai": "^4.2.7", 18 | "@types/mocha": "^5.2.7", 19 | "@types/node": "^10.17.10", 20 | "chai": "^4.2.0", 21 | "mocha": "^5.2.0", 22 | "radargun": "^1.0.1", 23 | "ts-mockito": "^2.5.0", 24 | "ts-node": "^7.0.1", 25 | "typescript": "^3.7.3" 26 | }, 27 | "scripts": { 28 | "build": "tsc", 29 | "test": "mocha --require ts-node/register test/**/*.ts", 30 | "test-p": "mocha --require ts-node/register test/StringReaderTest.ts", 31 | "benchmark": "radargun test/benchmarks/*.js" 32 | }, 33 | "author": "Remtori", 34 | "license": "MIT" 35 | } 36 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import CommandDispatcher from "./lib/CommandDispatcher" 2 | import LiteralMessage from "./lib/LiteralMessage" 3 | import ParseResults from "./lib/ParseResults" 4 | import StringReader from "./lib/StringReader" 5 | import { DefaultType } from "./lib/arguments/ArgumentType" 6 | import LiteralArgumentBuilder, { literal } from "./lib/builder/LiteralArgumentBuilder" 7 | import RequiredArgumentBuilder, { argument } from "./lib/builder/RequiredArgumentBuilder" 8 | import CommandContext from "./lib/context/CommandContext" 9 | import CommandContextBuilder from "./lib/context/CommandContextBuilder" 10 | import ParsedArgument from "./lib/context/ParsedArgument" 11 | import ParsedCommandNode from "./lib/context/ParsedCommandNode" 12 | import StringRange from "./lib/context/StringRange" 13 | import SuggestionsContext from "./lib/context/SuggestionContext" 14 | import CommandSyntaxException from "./lib/exceptions/CommandSyntaxException" 15 | import DynamicCommandExceptionType from "./lib/exceptions/DynamicCommandExceptionType" 16 | import SimpleCommandExceptionType from "./lib/exceptions/SimpleCommandExceptionType" 17 | import Suggestion from "./lib/suggestion/Suggestion" 18 | import Suggestions from "./lib/suggestion/Suggestions" 19 | import SuggestionsBuilder from "./lib/suggestion/SuggestionsBuilder"; 20 | import ArgumentCommandNode from "./lib/tree/ArgumentCommandNode" 21 | import LiteralCommandNode from "./lib/tree/LiteralCommandNode" 22 | import RootCommandNode from "./lib/tree/RootCommandNode" 23 | 24 | const { word, string, greedyString, bool, integer, float} = DefaultType 25 | 26 | module.exports = { 27 | dispatcher: new CommandDispatcher(), 28 | word, string, greedyString, bool, integer, float, 29 | literal, argument, 30 | CommandDispatcher, 31 | LiteralMessage, 32 | ParseResults, 33 | StringReader, 34 | LiteralArgumentBuilder, 35 | RequiredArgumentBuilder, 36 | CommandContext, 37 | CommandContextBuilder, 38 | ParsedArgument, 39 | ParsedCommandNode, 40 | StringRange, 41 | SuggestionsContext, 42 | CommandSyntaxException, 43 | SimpleCommandExceptionType, 44 | DynamicCommandExceptionType, 45 | Suggestion, 46 | Suggestions, 47 | SuggestionsBuilder, 48 | ArgumentCommandNode, 49 | LiteralCommandNode, 50 | RootCommandNode 51 | } -------------------------------------------------------------------------------- /src/lib/AmbiguityConsumer.ts: -------------------------------------------------------------------------------- 1 | import CommandNode from "./tree/CommandNode" 2 | 3 | export default interface AmbiguityConsumer { 4 | 5 | ambiguous(parent: CommandNode, child: CommandNode, sibling: CommandNode, inputs: Iterable): void; 6 | } -------------------------------------------------------------------------------- /src/lib/Command.ts: -------------------------------------------------------------------------------- 1 | import CommandContext from "./context/CommandContext" 2 | 3 | export default interface Command { 4 | (context: CommandContext): number; 5 | } -------------------------------------------------------------------------------- /src/lib/ImmutableStringReader.ts: -------------------------------------------------------------------------------- 1 | export default interface ImmutableStringReader { 2 | 3 | getString(): string; 4 | 5 | getRemainingLength(): number; 6 | 7 | getTotalLength(): number; 8 | 9 | getCursor(): number; 10 | 11 | getRead(): string; 12 | 13 | getRemaining(): string; 14 | 15 | canRead(length: number): boolean; 16 | 17 | canRead(): boolean; 18 | 19 | peek(): string; 20 | 21 | peek(offset: number): string; 22 | } -------------------------------------------------------------------------------- /src/lib/LiteralMessage.ts: -------------------------------------------------------------------------------- 1 | import Message from "./Message" 2 | 3 | export default class LiteralMessage implements Message { 4 | 5 | str: string; 6 | public constructor (str: string) { 7 | this.str = str; 8 | } 9 | 10 | public getString(): string { 11 | return this.str; 12 | } 13 | 14 | public toString(): string { 15 | return this.str; 16 | } 17 | } -------------------------------------------------------------------------------- /src/lib/Message.ts: -------------------------------------------------------------------------------- 1 | export default interface Message { 2 | getString(): string; 3 | } -------------------------------------------------------------------------------- /src/lib/ParseResults.ts: -------------------------------------------------------------------------------- 1 | import CommandContextBuilder from "./context/CommandContextBuilder" 2 | import CommandSyntaxException from "./exceptions/CommandSyntaxException" 3 | import CommandNode from "./tree/CommandNode" 4 | import ImmutableStringReader from "./ImmutableStringReader" 5 | import StringReader from "./StringReader" 6 | 7 | export default class ParseResults { 8 | 9 | private context: CommandContextBuilder; 10 | 11 | private exceptions: Map, CommandSyntaxException>; 12 | 13 | private reader: ImmutableStringReader; 14 | 15 | public constructor (context: CommandContextBuilder, reader: ImmutableStringReader, exceptions: Map, CommandSyntaxException>) { 16 | this.context = context; 17 | this.reader = reader || new StringReader(""); 18 | this.exceptions = exceptions || new Map, CommandSyntaxException>(); 19 | } 20 | 21 | public getContext(): CommandContextBuilder { 22 | return this.context; 23 | } 24 | 25 | public getReader(): ImmutableStringReader { 26 | return this.reader; 27 | } 28 | 29 | public getExceptions(): Map, CommandSyntaxException> { 30 | return this.exceptions; 31 | } 32 | } -------------------------------------------------------------------------------- /src/lib/Predicate.ts: -------------------------------------------------------------------------------- 1 | export default interface Predicate { 2 | (t: T): boolean; 3 | } -------------------------------------------------------------------------------- /src/lib/RedirectModifier.ts: -------------------------------------------------------------------------------- 1 | import CommandContext from "./context/CommandContext" 2 | 3 | export default interface RedirectModifier { 4 | apply(context: CommandContext): S[]; 5 | } 6 | -------------------------------------------------------------------------------- /src/lib/ResultConsumer.ts: -------------------------------------------------------------------------------- 1 | import CommandContext from "./context/CommandContext" 2 | 3 | export default interface ResultConsumer { 4 | 5 | onCommandComplete(context: CommandContext, success: boolean, result: number): void; 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/SingleRedirectModifier.ts: -------------------------------------------------------------------------------- 1 | import CommandContext from "./context/CommandContext" 2 | 3 | export default interface SingleRedirectModifier { 4 | 5 | apply(context: CommandContext): S; 6 | } -------------------------------------------------------------------------------- /src/lib/arguments/ArgumentType.ts: -------------------------------------------------------------------------------- 1 | import StringReader from "../StringReader" 2 | import CommandContext from "../context/CommandContext" 3 | import Suggestions from "../suggestion/Suggestions" 4 | import SuggestionsBuilder from "../suggestion/SuggestionsBuilder" 5 | 6 | import BoolArgumentType from "./BoolArgumentType" 7 | import IntegerArgumentType from "./IntegerArgumentType" 8 | import FloatArgumentType from "./FloatArgumentType" 9 | import StringArgumentType from "./StringArgumentType" 10 | 11 | export const DefaultType = { 12 | bool: BoolArgumentType.bool, 13 | integer: IntegerArgumentType.integer, 14 | float: FloatArgumentType.float, 15 | word: StringArgumentType.word, 16 | string: StringArgumentType.string, 17 | greedyString: StringArgumentType.greedyString 18 | } 19 | 20 | export default interface ArgumentType { 21 | 22 | parse(reader: StringReader): T; 23 | 24 | listSuggestions?(context: CommandContext, builder: SuggestionsBuilder): Promise; 25 | 26 | getExamples?(): Iterable; 27 | } -------------------------------------------------------------------------------- /src/lib/arguments/BoolArgumentType.ts: -------------------------------------------------------------------------------- 1 | import StringReader from "../StringReader" 2 | import CommandContext from "../context/CommandContext" 3 | import Suggestions from "../suggestion/Suggestions" 4 | import SuggestionsBuilder from "../suggestion/SuggestionsBuilder" 5 | import ArgumentType from "./ArgumentType" 6 | 7 | const EXAMPLES = ["true", "false"]; 8 | 9 | export default class BoolArgumentType implements ArgumentType { 10 | 11 | private constructor () { 12 | } 13 | 14 | public static bool(): BoolArgumentType { 15 | return new BoolArgumentType(); 16 | } 17 | 18 | public static getBool(context: CommandContext, name: string): boolean { 19 | return context.getArgument(name, Boolean); 20 | } 21 | 22 | public parse(reader: StringReader): boolean { 23 | return reader.readBoolean(); 24 | } 25 | 26 | public listSuggestions(context: CommandContext, builder: SuggestionsBuilder): Promise { 27 | if ("true".startsWith(builder.getRemaining().toLowerCase())) { 28 | builder.suggest("true"); 29 | } 30 | 31 | if ("false".startsWith(builder.getRemaining().toLowerCase())) { 32 | builder.suggest("false"); 33 | } 34 | 35 | return builder.buildPromise(); 36 | } 37 | 38 | public getExamples(): Iterable { 39 | return EXAMPLES; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/arguments/FloatArgumentType.ts: -------------------------------------------------------------------------------- 1 | import StringReader from "../StringReader" 2 | import CommandContext from "../context/CommandContext" 3 | import CommandSyntaxException from "../exceptions/CommandSyntaxException" 4 | import Suggestions from "../suggestion/Suggestions" 5 | import SuggestionsBuilder from "../suggestion/SuggestionsBuilder" 6 | import ArgumentType from "./ArgumentType" 7 | 8 | const EXAMPLES = ["0", "1.2", ".5", "-1", "-.5", "-1234.56"]; 9 | 10 | export default class FloatArgumentType implements ArgumentType { 11 | 12 | private minimum: number; 13 | private maximum: number; 14 | 15 | private constructor (minimum: number, maximum: number) { 16 | this.minimum = minimum; 17 | this.maximum = maximum; 18 | } 19 | 20 | public static float(min: number = -Infinity, max: number = Infinity): FloatArgumentType { 21 | return new FloatArgumentType(min, max); 22 | } 23 | 24 | public static getFloat(context: CommandContext, name: string): number { 25 | return context.getArgument(name, Number); 26 | } 27 | 28 | public getMinimum(): number { 29 | return this.minimum; 30 | } 31 | 32 | public getMaximum(): number { 33 | return this.maximum; 34 | } 35 | 36 | public parse(reader: StringReader): number { 37 | let start = reader.getCursor(); 38 | let result = reader.readFloat(); 39 | if (result < this.minimum) { 40 | reader.setCursor(start); 41 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooLow().createWithContext(reader, result, this.minimum); 42 | } 43 | 44 | if (result > this.maximum) { 45 | reader.setCursor(start); 46 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooHigh().createWithContext(reader, result, this.maximum); 47 | } 48 | 49 | return result; 50 | } 51 | 52 | public equals(o: object): boolean { 53 | if (this === o) return true; 54 | 55 | if (!(o instanceof FloatArgumentType)) return false; 56 | 57 | return this.maximum == o.maximum && this.minimum == o.minimum; 58 | } 59 | 60 | public toString(): string { 61 | if (this.minimum === -Infinity && this.maximum === Infinity) { 62 | return "float()"; 63 | } 64 | else if (this.maximum == Infinity) { 65 | return "float(" + this.minimum + ")"; 66 | } 67 | else { 68 | return "float(" + this.minimum + ", " + this.maximum + ")"; 69 | } 70 | } 71 | 72 | public getExamples(): Iterable { 73 | return EXAMPLES; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/lib/arguments/IntegerArgumentType.ts: -------------------------------------------------------------------------------- 1 | import StringReader from "../StringReader" 2 | import CommandContext from "../context/CommandContext" 3 | import CommandSyntaxException from "../exceptions/CommandSyntaxException" 4 | import Suggestions from "../suggestion/Suggestions" 5 | import SuggestionsBuilder from "../suggestion/SuggestionsBuilder" 6 | import ArgumentType from "./ArgumentType" 7 | 8 | const EXAMPLES = ["0", "123", "-123"]; 9 | 10 | export default class IntegerArgumentType implements ArgumentType { 11 | 12 | private minimum: number; 13 | private maximum: number; 14 | 15 | private constructor (minimum: number, maximum: number) { 16 | this.minimum = minimum; 17 | this.maximum = maximum; 18 | } 19 | 20 | public static integer(min: number = -Infinity, max: number = Infinity): IntegerArgumentType { 21 | return new IntegerArgumentType(min, max); 22 | } 23 | 24 | public static getInteger(context: CommandContext, name: string): number { 25 | return context.getArgument(name, Number); 26 | } 27 | 28 | public getMinimum(): number { 29 | return this.minimum; 30 | } 31 | 32 | public getMaximum(): number { 33 | return this.maximum; 34 | } 35 | 36 | public parse(reader: StringReader): number { 37 | let start = reader.getCursor(); 38 | let result = reader.readInt(); 39 | if (result < this.minimum) { 40 | reader.setCursor(start); 41 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooLow().createWithContext(reader, result, this.minimum); 42 | } 43 | 44 | if (result > this.maximum) { 45 | reader.setCursor(start); 46 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooHigh().createWithContext(reader, result, this.maximum); 47 | } 48 | 49 | return result; 50 | } 51 | 52 | public equals(o: object): boolean { 53 | if (this === o) return true; 54 | 55 | if (!(o instanceof IntegerArgumentType)) return false; 56 | 57 | return this.maximum == o.maximum && this.minimum == o.minimum; 58 | } 59 | 60 | public toString(): string { 61 | if (this.minimum === -Infinity && this.maximum === Infinity) { 62 | return "integer()"; 63 | } 64 | else if (this.maximum == Infinity) { 65 | return "integer(" + this.minimum + ")"; 66 | } 67 | else { 68 | return "integer(" + this.minimum + ", " + this.maximum + ")"; 69 | } 70 | } 71 | 72 | public getExamples(): Iterable { 73 | return EXAMPLES; 74 | } 75 | } -------------------------------------------------------------------------------- /src/lib/arguments/StringArgumentType.ts: -------------------------------------------------------------------------------- 1 | import StringReader from "../StringReader" 2 | import CommandContext from "../context/CommandContext" 3 | import Suggestions from "../suggestion/Suggestions" 4 | import SuggestionsBuilder from "../suggestion/SuggestionsBuilder" 5 | import ArgumentType from "./ArgumentType" 6 | 7 | export enum StringType { 8 | SINGLE_WORD = "words_with_underscores", 9 | QUOTABLE_PHRASE = "\"quoted phrase\"", 10 | GREEDY_PHRASE = "words with spaces" 11 | } 12 | 13 | export default class StringArgumentType implements ArgumentType { 14 | 15 | private type: StringType; 16 | 17 | private constructor (type: StringType) { 18 | this.type = type; 19 | } 20 | 21 | public static word(): StringArgumentType { 22 | return new StringArgumentType(StringType.SINGLE_WORD); 23 | } 24 | 25 | public static string(): StringArgumentType { 26 | return new StringArgumentType(StringType.QUOTABLE_PHRASE); 27 | } 28 | 29 | public static greedyString(): StringArgumentType { 30 | return new StringArgumentType(StringType.GREEDY_PHRASE); 31 | } 32 | 33 | public static getString(context: CommandContext, name: string): string { 34 | return context.getArgument(name, String); 35 | } 36 | 37 | public getType(): StringType { 38 | return this.type; 39 | } 40 | 41 | public parse(reader: StringReader): string { 42 | if (this.type == StringType.GREEDY_PHRASE) { 43 | let text = reader.getRemaining(); 44 | reader.setCursor(reader.getTotalLength()); 45 | return text; 46 | } 47 | else if (this.type == StringType.SINGLE_WORD) { 48 | return reader.readUnquotedString(); 49 | } 50 | else { 51 | return reader.readString(); 52 | } 53 | 54 | } 55 | 56 | public toString(): string { 57 | return "string()"; 58 | } 59 | 60 | public static escapeIfRequired(input: string): String { 61 | for (let c of input) { 62 | if (!StringReader.isAllowedInUnquotedString(c)) { 63 | return StringArgumentType.escape(input); 64 | } 65 | } 66 | 67 | return input; 68 | } 69 | 70 | private static escape(input: string): string { 71 | let result = "\""; 72 | for (let i = 0; i < input.length; i++) { 73 | const c = input.charAt(i); 74 | if (c == '\\' || c == '"') { 75 | result += '\\'; 76 | } 77 | result += c; 78 | } 79 | 80 | result += "\""; 81 | return result; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/lib/builder/ArgumentBuilder.ts: -------------------------------------------------------------------------------- 1 | import Command from "../Command" 2 | import RedirectModifier from "../RedirectModifier" 3 | import SingleRedirectModifier from "../SingleRedirectModifier" 4 | import CommandNode from "../tree/CommandNode" 5 | import RootCommandNode from "../tree/RootCommandNode" 6 | import Predicate from "../Predicate" 7 | import CommandContext from "../context/CommandContext"; 8 | 9 | abstract class ArgumentBuilder> { 10 | 11 | private args: RootCommandNode = new RootCommandNode(); 12 | 13 | private command: Command; 14 | 15 | private requirement: Predicate; 16 | 17 | private target: CommandNode; 18 | 19 | private modifier: RedirectModifier = null; 20 | 21 | private forks: boolean; 22 | 23 | public abstract getThis(): T; 24 | 25 | public then(arg: ArgumentBuilder | CommandNode): T { 26 | if (!(this.target == null)) { 27 | throw new Error("Cannot add children to a redirected node"); 28 | } 29 | 30 | if (arg instanceof CommandNode) 31 | this.args.addChild(arg) 32 | else 33 | this.args.addChild(arg.build()); 34 | 35 | return this.getThis(); 36 | } 37 | 38 | public getArguments(): Iterable> { 39 | return this.args.getChildren(); 40 | } 41 | 42 | public executes(command: Command): T { 43 | this.command = command; 44 | return this.getThis(); 45 | } 46 | 47 | public getCommand(): Command { 48 | return this.command; 49 | } 50 | 51 | public requires(requirement: Predicate): T { 52 | this.requirement = requirement; 53 | return this.getThis(); 54 | } 55 | 56 | public getRequirement(): Predicate { 57 | return this.requirement; 58 | } 59 | 60 | public redirect(target: CommandNode, modifier?: SingleRedirectModifier): T { 61 | return this.forward(target, modifier == null ? null : (o: CommandContext) => [ modifier.apply(o) ], false); 62 | } 63 | 64 | public fork(target: CommandNode, modifier: RedirectModifier): T { 65 | return this.forward(target, modifier, true); 66 | } 67 | 68 | public forward(target: CommandNode, modifier: RedirectModifier, fork: boolean): T { 69 | if (this.args.getChildrenCount() > 0) { 70 | throw new Error("Cannot forward a node with children"); 71 | } 72 | 73 | this.target = target; 74 | this.modifier = modifier; 75 | this.forks = fork; 76 | return this.getThis(); 77 | } 78 | 79 | public getRedirect(): CommandNode { 80 | return this.target; 81 | } 82 | 83 | public getRedirectModifier(): RedirectModifier { 84 | return this.modifier; 85 | } 86 | 87 | public isFork(): boolean { 88 | return this.forks; 89 | } 90 | 91 | public abstract build(): CommandNode; 92 | } 93 | 94 | export default ArgumentBuilder; -------------------------------------------------------------------------------- /src/lib/builder/LiteralArgumentBuilder.ts: -------------------------------------------------------------------------------- 1 | import LiteralCommandNode from "../tree/LiteralCommandNode" 2 | import ArgumentBuilder from "./ArgumentBuilder" 3 | 4 | export default class LiteralArgumentBuilder extends ArgumentBuilder> { 5 | 6 | private literal: string; 7 | 8 | public constructor (literal: string) { 9 | super(); 10 | this.literal = literal; 11 | } 12 | 13 | public static literal(name: string): LiteralArgumentBuilder { 14 | return new LiteralArgumentBuilder(name); 15 | } 16 | 17 | public getThis(): LiteralArgumentBuilder { 18 | return this; 19 | } 20 | 21 | public getLiteral(): string { 22 | return this.literal; 23 | } 24 | 25 | public build(): LiteralCommandNode { 26 | let result: LiteralCommandNode = new LiteralCommandNode(this.getLiteral(), this.getCommand(), this.getRequirement(), this.getRedirect(), this.getRedirectModifier(), this.isFork()); 27 | for (let arg of this.getArguments()) { 28 | result.addChild(arg); 29 | } 30 | 31 | return result; 32 | } 33 | } 34 | 35 | export const literal = LiteralArgumentBuilder.literal; -------------------------------------------------------------------------------- /src/lib/builder/RequiredArgumentBuilder.ts: -------------------------------------------------------------------------------- 1 | import ArgumentType from "../arguments/ArgumentType" 2 | import SuggestionProvider from "../suggestion/SuggestionProvider" 3 | import ArgumentCommandNode from "../tree/ArgumentCommandNode" 4 | import ArgumentBuilder from "./ArgumentBuilder" 5 | 6 | export default class RequiredArgumentBuilder extends ArgumentBuilder> { 7 | 8 | private name: string; 9 | 10 | private type: ArgumentType; 11 | 12 | private suggestionsProvider: SuggestionProvider; 13 | 14 | private constructor (name: string, type: ArgumentType) { 15 | super(); 16 | this.name = name; 17 | this.type = type; 18 | } 19 | 20 | public static argument(name: string, type: ArgumentType): RequiredArgumentBuilder { 21 | return new RequiredArgumentBuilder(name, type); 22 | } 23 | 24 | public suggests(provider: SuggestionProvider): RequiredArgumentBuilder { 25 | this.suggestionsProvider = provider; 26 | return this.getThis(); 27 | } 28 | 29 | public getSuggestionsProvider(): SuggestionProvider { 30 | return this.suggestionsProvider; 31 | } 32 | 33 | public getThis(): RequiredArgumentBuilder { 34 | return this; 35 | } 36 | 37 | public getType(): ArgumentType { 38 | return this.type; 39 | } 40 | 41 | public getName(): string { 42 | return this.name; 43 | } 44 | 45 | public build(): ArgumentCommandNode { 46 | let result: ArgumentCommandNode = new ArgumentCommandNode(this.getName(), this.getType(), this.getCommand(), this.getRequirement(), this.getRedirect(), this.getRedirectModifier(), this.isFork(), this.getSuggestionsProvider()); 47 | for (let arg of this.getArguments()) { 48 | result.addChild(arg); 49 | } 50 | return result; 51 | } 52 | } 53 | 54 | export const argument = RequiredArgumentBuilder.argument; -------------------------------------------------------------------------------- /src/lib/context/CommandContext.ts: -------------------------------------------------------------------------------- 1 | import isEqual from "../util/isEqual" 2 | import Command from "../Command" 3 | import RedirectModifier from "../RedirectModifier" 4 | import CommandNode from "../tree/CommandNode" 5 | import StringRange from "./StringRange" 6 | import ParsedArgument from "./ParsedArgument" 7 | import ParsedCommandNode from "./ParsedCommandNode" 8 | 9 | export default class CommandContext { 10 | 11 | private source: S; 12 | private input: string; 13 | private command: Command; 14 | private args: Map>; 15 | private rootNode: CommandNode; 16 | private nodes: Array>; 17 | private range: StringRange; 18 | private child: CommandContext; 19 | private modifier: RedirectModifier; 20 | private forks: boolean; 21 | 22 | public constructor (source: S, input: string, args: Map>, command: Command, rootNode: CommandNode, nodes: Array>, range: StringRange, child: CommandContext, modifier: RedirectModifier, forks: boolean) { 23 | this.source = source; 24 | this.input = input; 25 | this.args = args; 26 | this.command = command; 27 | this.rootNode = rootNode; 28 | this.nodes = nodes; 29 | this.range = range; 30 | this.child = child; 31 | this.modifier = modifier; 32 | this.forks = forks; 33 | } 34 | 35 | public copyFor(source: S): CommandContext { 36 | if (this.source === source) 37 | return this; 38 | 39 | return new CommandContext(source, this.input, this.args, this.command, this.rootNode, this.nodes, this.range, this.child, this.modifier, this.forks); 40 | } 41 | 42 | public getChild(): CommandContext { 43 | return this.child; 44 | } 45 | 46 | public getLastChild(): CommandContext { 47 | let result: CommandContext = this; 48 | while (!(result.getChild() == null)) { 49 | result = result.getChild(); 50 | } 51 | return result; 52 | } 53 | 54 | public getCommand(): Command { 55 | return this.command; 56 | } 57 | 58 | public getSource(): S { 59 | return this.source; 60 | } 61 | 62 | public getArgument(name: string, clazz?: Function): any { 63 | const arg: ParsedArgument = this.args.get(name); 64 | 65 | if (arg == null) { 66 | throw new Error("No such argument '" + name + "' exists on this command"); 67 | } 68 | 69 | let result = arg.getResult(); 70 | if (clazz == null) { 71 | return result; 72 | } else { 73 | return clazz(result); 74 | } 75 | } 76 | 77 | public equals(o: object): boolean { 78 | if (this === o) return true; 79 | if (!(o instanceof CommandContext)) return false; 80 | 81 | if (!isEqual(this.args, o.args)) return false; 82 | if (!this.rootNode.equals(o.rootNode)) return false; 83 | if (this.nodes.length != o.nodes.length || !isEqual(this.nodes, o.nodes)) return false; 84 | if (!(this.command == null) ? !isEqual(this.command, o.command) : o.command != null) return false; 85 | if (!isEqual(this.source, o.source)) return false; 86 | if (!(this.child == null) ? !this.child.equals(o.child) : o.child != null) return false; 87 | 88 | return true; 89 | } 90 | 91 | public getRedirectModifier(): RedirectModifier { 92 | return this.modifier; 93 | } 94 | 95 | public getRange(): StringRange { 96 | return this.range; 97 | } 98 | 99 | public getInput(): string { 100 | return this.input; 101 | } 102 | 103 | public getRootNode(): CommandNode { 104 | return this.rootNode; 105 | } 106 | 107 | public getNodes(): Array> { 108 | return this.nodes; 109 | } 110 | 111 | public hasNodes(): boolean { 112 | return this.nodes.length >= 0 113 | } 114 | 115 | public isForked(): boolean { 116 | return this.forks; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/lib/context/CommandContextBuilder.ts: -------------------------------------------------------------------------------- 1 | import Command from "../Command" 2 | import StringRange from "./StringRange" 3 | import CommandNode from "../tree/CommandNode" 4 | import CommandContext from "./CommandContext" 5 | import ParsedArgument from "./ParsedArgument" 6 | import SuggestionContext from "./SuggestionContext" 7 | import ParsedCommandNode from "./ParsedCommandNode" 8 | import CommandDispatcher from "../CommandDispatcher" 9 | import RedirectModifier from "../RedirectModifier" 10 | 11 | export default class CommandContextBuilder { 12 | 13 | private args: Map> = new Map(); 14 | 15 | private rootNode: CommandNode; 16 | 17 | private nodes: Array> = []; 18 | 19 | private dispatcher: CommandDispatcher; 20 | 21 | private source: S; 22 | 23 | private command: Command; 24 | 25 | private child: CommandContextBuilder; 26 | 27 | private range: StringRange; 28 | 29 | private modifier: RedirectModifier = null; 30 | 31 | private forks: boolean; 32 | 33 | public constructor (dispatcher: CommandDispatcher, source: S, rootNode: CommandNode, start: number) { 34 | this.rootNode = rootNode; 35 | this.dispatcher = dispatcher; 36 | this.source = source; 37 | this.range = StringRange.at(start); 38 | } 39 | 40 | public withSource(source: S): CommandContextBuilder { 41 | this.source = source; 42 | return this; 43 | } 44 | 45 | public getSource(): S { 46 | return this.source; 47 | } 48 | 49 | public getRootNode(): CommandNode { 50 | return this.rootNode; 51 | } 52 | 53 | public withArgument(name: String, argument: ParsedArgument): CommandContextBuilder { 54 | this.args.set(name, argument); 55 | return this; 56 | } 57 | 58 | public getArguments(): Map> { 59 | return this.args; 60 | } 61 | 62 | public withCommand(command: Command): CommandContextBuilder { 63 | this.command = command; 64 | return this; 65 | } 66 | 67 | public withNode(node: CommandNode, range: StringRange): CommandContextBuilder { 68 | this.nodes.push(new ParsedCommandNode(node, range)); 69 | this.range = StringRange.encompassing(this.range, range); 70 | this.modifier = node.getRedirectModifier(); 71 | this.forks = node.isFork(); 72 | return this; 73 | } 74 | 75 | public copy(): CommandContextBuilder { 76 | const copy: CommandContextBuilder = new CommandContextBuilder(this.dispatcher, this.source, this.rootNode, this.range.getStart()); 77 | copy.command = this.command; 78 | copy.args = new Map([...copy.args, ...this.args]) 79 | copy.nodes.push(...this.nodes); 80 | copy.child = this.child; 81 | copy.range = this.range; 82 | copy.forks = this.forks; 83 | return copy; 84 | } 85 | 86 | public withChild(child: CommandContextBuilder): CommandContextBuilder { 87 | this.child = child; 88 | return this; 89 | } 90 | 91 | public getChild(): CommandContextBuilder { 92 | return this.child; 93 | } 94 | 95 | public getLastChild(): CommandContextBuilder { 96 | let result: CommandContextBuilder = this; 97 | while (result.getChild() != null) { 98 | result = result.getChild(); 99 | } 100 | 101 | return result; 102 | } 103 | 104 | public getCommand(): Command { 105 | return this.command; 106 | } 107 | 108 | public getNodes(): Array> { 109 | return this.nodes; 110 | } 111 | 112 | public build(input: string): CommandContext { 113 | return new CommandContext(this.source, input, this.args, this.command, this.rootNode, this.nodes, this.range, this.child == null ? null : this.child.build(input), this.modifier, this.forks); 114 | } 115 | 116 | public getDispatcher(): CommandDispatcher { 117 | return this.dispatcher; 118 | } 119 | 120 | public getRange(): StringRange { 121 | return this.range; 122 | } 123 | 124 | public findSuggestionContext(cursor: number): SuggestionContext { 125 | if ((this.range.getStart() <= cursor)) { 126 | if ((this.range.getEnd() < cursor)) { 127 | if ((this.child != null)) { 128 | return this.child.findSuggestionContext(cursor); 129 | } 130 | else if (this.nodes.length > 0) { 131 | let last: ParsedCommandNode = this.nodes[this.nodes.length - 1]; 132 | return new SuggestionContext(last.getNode(), last.getRange().getEnd() + 1); 133 | } 134 | else { 135 | return new SuggestionContext(this.rootNode, this.range.getStart()); 136 | } 137 | } 138 | else { 139 | let prev: CommandNode = this.rootNode; 140 | for (let node of this.nodes) { 141 | let nodeRange: StringRange = node.getRange(); 142 | if (nodeRange.getStart() <= cursor && cursor <= nodeRange.getEnd()) { 143 | return new SuggestionContext(prev, nodeRange.getStart()); 144 | } 145 | prev = node.getNode(); 146 | } 147 | if ((prev == null)) { 148 | throw new Error("Can't find node before cursor"); 149 | } 150 | return new SuggestionContext(prev, this.range.getStart()); 151 | } 152 | } 153 | throw new Error("Can't find node before cursor"); 154 | } 155 | } -------------------------------------------------------------------------------- /src/lib/context/ParsedArgument.ts: -------------------------------------------------------------------------------- 1 | import StringRange from "./StringRange" 2 | 3 | export default class ParsedArgument { 4 | 5 | private range: StringRange; 6 | private result: T; 7 | 8 | public constructor (start: number, end: number, result: T) { 9 | this.range = StringRange.between(start, end); 10 | this.result = result; 11 | } 12 | 13 | public getRange(): StringRange { 14 | return this.range; 15 | } 16 | 17 | public getResult(): T { 18 | return this.result; 19 | } 20 | 21 | public equals(o: object): boolean { 22 | if (this === o) return true; 23 | if (!(o instanceof ParsedArgument)) return false; 24 | 25 | return this.range.equals(o.range) && this.result === o.result; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/context/ParsedCommandNode.ts: -------------------------------------------------------------------------------- 1 | import CommandNode from "../tree/CommandNode" 2 | import StringRange from "./StringRange" 3 | 4 | export default class ParsedCommandNode { 5 | 6 | private node: CommandNode; 7 | private range: StringRange; 8 | 9 | public constructor (node: CommandNode, range: StringRange) { 10 | this.node = node; 11 | this.range = range; 12 | } 13 | 14 | public getNode(): CommandNode { 15 | return this.node; 16 | } 17 | 18 | public getRange(): StringRange { 19 | return this.range; 20 | } 21 | 22 | public toString(): String { 23 | return this.node + "@" + this.range; 24 | } 25 | 26 | public equals(o: object): boolean { 27 | if (this === o) return true; 28 | 29 | if (o == null || !(o instanceof ParsedCommandNode)) { 30 | return false; 31 | } 32 | 33 | return this.node.equals(o.node) && this.range.equals(o.range); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/context/StringRange.ts: -------------------------------------------------------------------------------- 1 | import ImmutableStringReader from "../ImmutableStringReader" 2 | 3 | export default class StringRange { 4 | 5 | private start: number; 6 | private end: number; 7 | 8 | public constructor (start: number, end: number) { 9 | this.start = start; 10 | this.end = end; 11 | } 12 | 13 | public static at(pos: number): StringRange { 14 | return new StringRange(pos, pos); 15 | } 16 | 17 | public static between(start: number, end: number): StringRange { 18 | return new StringRange(start, end); 19 | } 20 | 21 | public static encompassing(a: StringRange, b: StringRange): StringRange { 22 | return new StringRange(Math.min(a.getStart(), b.getStart()), Math.max(a.getEnd(), b.getEnd())); 23 | } 24 | 25 | public getStart(): number { 26 | return this.start; 27 | } 28 | 29 | public getEnd(): number { 30 | return this.end; 31 | } 32 | 33 | public get(str: ImmutableStringReader | string): string { 34 | if (typeof str === "string") 35 | return str.substring(this.start, this.end); 36 | else 37 | return str.getString().substring(this.start, this.end); 38 | } 39 | 40 | public isEmpty(): boolean { 41 | return this.start === this.end; 42 | } 43 | 44 | public getLength(): number { 45 | return this.end - this.start; 46 | } 47 | 48 | public equals(o: object): boolean { 49 | if (this === o) return true; 50 | if (!(o instanceof StringRange)) return false; 51 | return this.start === o.start && this.end == o.end; 52 | } 53 | 54 | public toString(): string { 55 | return "StringRange{" + "start=" + this.start + ", end=" + this.end + '}'; 56 | } 57 | } -------------------------------------------------------------------------------- /src/lib/context/SuggestionContext.ts: -------------------------------------------------------------------------------- 1 | import CommandNode from "../tree/CommandNode" 2 | 3 | export default class SuggestionContext { 4 | 5 | public parent: CommandNode; 6 | public startPos: number; 7 | 8 | public constructor (parent: CommandNode, startPos: number) { 9 | this.parent = parent; 10 | this.startPos = startPos; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/exceptions/BuiltInExceptionProvider.ts: -------------------------------------------------------------------------------- 1 | import DynamicCommandExceptionType from "./DynamicCommandExceptionType" 2 | import SimpleCommandExceptionType from "./SimpleCommandExceptionType" 3 | 4 | export default interface BuiltInExceptionProvider { 5 | 6 | floatTooLow(): DynamicCommandExceptionType; 7 | 8 | floatTooHigh(): DynamicCommandExceptionType; 9 | 10 | integerTooLow(): DynamicCommandExceptionType; 11 | 12 | integerTooHigh(): DynamicCommandExceptionType; 13 | 14 | literalIncorrect(): DynamicCommandExceptionType; 15 | 16 | readerExpectedStartOfQuote(): SimpleCommandExceptionType; 17 | 18 | readerExpectedEndOfQuote(): SimpleCommandExceptionType; 19 | 20 | readerInvalidEscape(): DynamicCommandExceptionType; 21 | 22 | readerInvalidBool(): DynamicCommandExceptionType; 23 | 24 | readerInvalidInt(): DynamicCommandExceptionType; 25 | 26 | readerExpectedInt(): SimpleCommandExceptionType; 27 | 28 | readerInvalidFloat(): DynamicCommandExceptionType; 29 | 30 | readerExpectedFloat(): SimpleCommandExceptionType; 31 | 32 | readerExpectedBool(): SimpleCommandExceptionType; 33 | 34 | readerExpectedSymbol(): DynamicCommandExceptionType; 35 | 36 | dispatcherUnknownCommand(): SimpleCommandExceptionType; 37 | 38 | dispatcherUnknownArgument(): SimpleCommandExceptionType; 39 | 40 | dispatcherExpectedArgumentSeparator(): SimpleCommandExceptionType; 41 | 42 | dispatcherParseException(): DynamicCommandExceptionType; 43 | } -------------------------------------------------------------------------------- /src/lib/exceptions/BuiltInExceptions.ts: -------------------------------------------------------------------------------- 1 | import LiteralMessage from "../LiteralMessage" 2 | import BuiltInExceptionProvider from "./BuiltInExceptionProvider" 3 | import SimpleCommandExceptionType from "./SimpleCommandExceptionType" 4 | import DynamicCommandExceptionType from "./DynamicCommandExceptionType" 5 | 6 | export default class BuiltInExceptions implements BuiltInExceptionProvider { 7 | 8 | private static FLOAT_TOO_SMALL = new DynamicCommandExceptionType((found, min) => new LiteralMessage("Float must not be less than " + min + ", found " + found)); 9 | 10 | private static FLOAT_TOO_BIG = new DynamicCommandExceptionType((found, max) => new LiteralMessage("Float must not be more than " + max + ", found " + found)); 11 | 12 | private static INTEGER_TOO_SMALL = new DynamicCommandExceptionType((found, min) => new LiteralMessage("Integer must not be less than " + min + ", found " + found)); 13 | 14 | private static INTEGER_TOO_BIG = new DynamicCommandExceptionType((found, max) => new LiteralMessage("Integer must not be more than " + max + ", found " + found)); 15 | 16 | private static LITERAL_INCORRECT: DynamicCommandExceptionType = new DynamicCommandExceptionType(expected => new LiteralMessage("Expected literal " + expected)); 17 | 18 | private static READER_EXPECTED_START_OF_QUOTE: SimpleCommandExceptionType = new SimpleCommandExceptionType(new LiteralMessage("Expected quote to start a string")); 19 | 20 | private static READER_EXPECTED_END_OF_QUOTE: SimpleCommandExceptionType = new SimpleCommandExceptionType(new LiteralMessage("Unclosed quoted string")); 21 | 22 | private static READER_INVALID_ESCAPE: DynamicCommandExceptionType = new DynamicCommandExceptionType(character => new LiteralMessage("Invalid escape sequence '" + character + "' in quoted string")); 23 | 24 | private static READER_INVALID_BOOL: DynamicCommandExceptionType = new DynamicCommandExceptionType(value => new LiteralMessage("Invalid bool, expected true or false but found '" + value + "'")); 25 | 26 | private static READER_INVALID_INT: DynamicCommandExceptionType = new DynamicCommandExceptionType(value => new LiteralMessage("Invalid integer '" + value + "'")); 27 | 28 | private static READER_EXPECTED_INT: SimpleCommandExceptionType = new SimpleCommandExceptionType(new LiteralMessage("Expected integer")); 29 | 30 | private static READER_INVALID_FLOAT: DynamicCommandExceptionType = new DynamicCommandExceptionType(value => new LiteralMessage("Invalid float '" + value + "'")); 31 | 32 | private static READER_EXPECTED_FLOAT: SimpleCommandExceptionType = new SimpleCommandExceptionType(new LiteralMessage("Expected float")); 33 | 34 | private static READER_EXPECTED_BOOL: SimpleCommandExceptionType = new SimpleCommandExceptionType(new LiteralMessage("Expected bool")); 35 | 36 | private static READER_EXPECTED_SYMBOL: DynamicCommandExceptionType = new DynamicCommandExceptionType(symbol => new LiteralMessage("Expected '" + symbol + "'")); 37 | 38 | private static DISPATCHER_UNKNOWN_COMMAND: SimpleCommandExceptionType = new SimpleCommandExceptionType(new LiteralMessage("Unknown command")); 39 | 40 | private static DISPATCHER_UNKNOWN_ARGUMENT: SimpleCommandExceptionType = new SimpleCommandExceptionType(new LiteralMessage("Incorrect argument for command")); 41 | 42 | private static DISPATCHER_EXPECTED_ARGUMENT_SEPARATOR: SimpleCommandExceptionType = new SimpleCommandExceptionType(new LiteralMessage("Expected whitespace to end one argument, but found trailing data")); 43 | 44 | private static DISPATCHER_PARSE_EXCEPTION: DynamicCommandExceptionType = new DynamicCommandExceptionType(message => new LiteralMessage(("Could not parse command: " + message))); 45 | 46 | public floatTooLow(): DynamicCommandExceptionType { 47 | return BuiltInExceptions.FLOAT_TOO_SMALL; 48 | } 49 | 50 | public floatTooHigh(): DynamicCommandExceptionType { 51 | return BuiltInExceptions.FLOAT_TOO_BIG; 52 | } 53 | 54 | public integerTooLow(): DynamicCommandExceptionType { 55 | return BuiltInExceptions.INTEGER_TOO_SMALL; 56 | } 57 | 58 | public integerTooHigh(): DynamicCommandExceptionType { 59 | return BuiltInExceptions.INTEGER_TOO_BIG; 60 | } 61 | 62 | public literalIncorrect(): DynamicCommandExceptionType { 63 | return BuiltInExceptions.LITERAL_INCORRECT; 64 | } 65 | 66 | public readerExpectedStartOfQuote(): SimpleCommandExceptionType { 67 | return BuiltInExceptions.READER_EXPECTED_START_OF_QUOTE; 68 | } 69 | 70 | public readerExpectedEndOfQuote(): SimpleCommandExceptionType { 71 | return BuiltInExceptions.READER_EXPECTED_END_OF_QUOTE; 72 | } 73 | 74 | public readerInvalidEscape(): DynamicCommandExceptionType { 75 | return BuiltInExceptions.READER_INVALID_ESCAPE; 76 | } 77 | 78 | public readerInvalidBool(): DynamicCommandExceptionType { 79 | return BuiltInExceptions.READER_INVALID_BOOL; 80 | } 81 | 82 | public readerInvalidInt(): DynamicCommandExceptionType { 83 | return BuiltInExceptions.READER_INVALID_INT; 84 | } 85 | 86 | public readerExpectedInt(): SimpleCommandExceptionType { 87 | return BuiltInExceptions.READER_EXPECTED_INT; 88 | } 89 | 90 | public readerInvalidFloat(): DynamicCommandExceptionType { 91 | return BuiltInExceptions.READER_INVALID_FLOAT; 92 | } 93 | 94 | public readerExpectedFloat(): SimpleCommandExceptionType { 95 | return BuiltInExceptions.READER_EXPECTED_FLOAT; 96 | } 97 | 98 | public readerExpectedBool(): SimpleCommandExceptionType { 99 | return BuiltInExceptions.READER_EXPECTED_BOOL; 100 | } 101 | 102 | public readerExpectedSymbol(): DynamicCommandExceptionType { 103 | return BuiltInExceptions.READER_EXPECTED_SYMBOL; 104 | } 105 | 106 | public dispatcherUnknownCommand(): SimpleCommandExceptionType { 107 | return BuiltInExceptions.DISPATCHER_UNKNOWN_COMMAND; 108 | } 109 | 110 | public dispatcherUnknownArgument(): SimpleCommandExceptionType { 111 | return BuiltInExceptions.DISPATCHER_UNKNOWN_ARGUMENT; 112 | } 113 | 114 | public dispatcherExpectedArgumentSeparator(): SimpleCommandExceptionType { 115 | return BuiltInExceptions.DISPATCHER_EXPECTED_ARGUMENT_SEPARATOR; 116 | } 117 | 118 | public dispatcherParseException(): DynamicCommandExceptionType { 119 | return BuiltInExceptions.DISPATCHER_PARSE_EXCEPTION; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/lib/exceptions/CommandExceptionType.ts: -------------------------------------------------------------------------------- 1 | export default interface CommandExceptionType { 2 | } 3 | -------------------------------------------------------------------------------- /src/lib/exceptions/CommandSyntaxException.ts: -------------------------------------------------------------------------------- 1 | import Message from "../Message" 2 | import BuiltInExceptions from "./BuiltInExceptions" 3 | import BuiltInExceptionProvider from "./BuiltInExceptionProvider" 4 | import CommandExceptionType from "./CommandExceptionType" 5 | 6 | export default class CommandSyntaxException extends Error { 7 | 8 | public static CONTEXT_AMOUNT = 10; 9 | public static BUILT_IN_EXCEPTIONS: BuiltInExceptionProvider = new BuiltInExceptions(); 10 | 11 | private type: CommandExceptionType; 12 | private __message: Message; 13 | private input: string; 14 | private cursor: number; 15 | 16 | public constructor (type: CommandExceptionType, message: Message, input: string = null, cursor = -1) { 17 | super(message.getString()); 18 | Error.captureStackTrace(this, CommandSyntaxException) 19 | this.type = type; 20 | this.__message = message; 21 | this.input = input; 22 | this.cursor = cursor; 23 | 24 | this.message = this.getMessage(); 25 | } 26 | 27 | public getMessage(): string { 28 | let message = this.__message.getString(); 29 | let context = this.getContext(); 30 | if (context != null) { 31 | message += " at position " + this.cursor + ": " + context; 32 | } 33 | return message; 34 | } 35 | 36 | public getRawMessage(): Message { 37 | return this.__message; 38 | } 39 | 40 | public getContext(): string { 41 | if (this.input == null || this.cursor < 0) { 42 | return null; 43 | } 44 | 45 | let builder = "" 46 | let cursor = Math.min(this.input.length, this.cursor); 47 | if (cursor > CommandSyntaxException.CONTEXT_AMOUNT) { 48 | builder += "..."; 49 | } 50 | 51 | builder += this.input.substring( 52 | Math.max( 53 | 0, 54 | cursor - CommandSyntaxException.CONTEXT_AMOUNT 55 | ), 56 | cursor 57 | ); 58 | builder += "<--[HERE]"; 59 | return builder; 60 | } 61 | 62 | public getType(): CommandExceptionType { 63 | return this.type; 64 | } 65 | 66 | public getInput(): string { 67 | return this.input; 68 | } 69 | 70 | public getCursor(): number { 71 | return this.cursor; 72 | } 73 | 74 | public toString(): string { 75 | return this.message; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/lib/exceptions/DynamicCommandExceptionType.ts: -------------------------------------------------------------------------------- 1 | import Message from "../Message" 2 | import CommandExceptionType from "./CommandExceptionType" 3 | import CommandSyntaxException from "./CommandSyntaxException" 4 | import ImmutableStringReader from "../ImmutableStringReader" 5 | 6 | export default class DynamicCommandExceptionType implements CommandExceptionType { 7 | 8 | private fn: Function; 9 | 10 | public constructor(fn: (...args: any[]) => Message) { 11 | this.fn = fn; 12 | Error.captureStackTrace(this, DynamicCommandExceptionType) 13 | } 14 | 15 | public create(...args: any[]): CommandSyntaxException { 16 | return new CommandSyntaxException(this, this.fn(...args)); 17 | } 18 | 19 | public createWithContext(reader: ImmutableStringReader, ...args: any[]): CommandSyntaxException { 20 | return new CommandSyntaxException(this, this.fn(...args), reader.getString(), reader.getCursor()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/exceptions/SimpleCommandExceptionType.ts: -------------------------------------------------------------------------------- 1 | import Message from "../Message" 2 | import ImmutableStringReader from "../ImmutableStringReader" 3 | import CommandExceptionType from "./CommandExceptionType" 4 | import CommandSyntaxException from "./CommandSyntaxException"; 5 | 6 | export default class SimpleCommandExceptionType implements CommandExceptionType { 7 | 8 | private message: Message; 9 | 10 | public constructor (message: Message) { 11 | this.message = message; 12 | Error.captureStackTrace(this, SimpleCommandExceptionType) 13 | } 14 | 15 | public create(): CommandSyntaxException { 16 | return new CommandSyntaxException(this, this.message) 17 | } 18 | 19 | public createWithContext(reader: ImmutableStringReader): CommandSyntaxException { 20 | return new CommandSyntaxException(this, this.message, reader.getString(), reader.getCursor()); 21 | } 22 | 23 | public toString(): string { 24 | return this.message.getString(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/suggestion/IntegerSuggestion.ts: -------------------------------------------------------------------------------- 1 | import Message from "../Message" 2 | import StringRange from "../context/StringRange" 3 | import Suggestion from "./Suggestion" 4 | 5 | export default class IntegerSuggestion extends Suggestion { 6 | 7 | private value: number; 8 | 9 | public constructor (range: StringRange, value: number, tooltip: Message = null) { 10 | super(range, value.toString(), tooltip); 11 | this.value = value; 12 | } 13 | 14 | public getValue(): number { 15 | return this.value; 16 | } 17 | 18 | public equals(o: object): boolean { 19 | if (this === o) return true; 20 | 21 | if (!(o instanceof IntegerSuggestion)) return false; 22 | 23 | return this.value == o.value && super.equals(o); 24 | } 25 | 26 | public toString(): String { 27 | return "IntegerSuggestion{" + 28 | "value=" + this.value + 29 | ", range=" + this.getRange() + 30 | ", text='" + this.getText() + '\'' + 31 | ", tooltip='" + this.getTooltip() + '\'' + 32 | '}'; 33 | } 34 | 35 | public compareTo(o: Suggestion): number { 36 | if (o instanceof IntegerSuggestion) { 37 | return this.value < o.value ? 1 : - 1; 38 | } 39 | 40 | return super.compareTo(o); 41 | } 42 | 43 | public compareToIgnoreCase(b: Suggestion): number { 44 | return this.compareTo(b); 45 | } 46 | } -------------------------------------------------------------------------------- /src/lib/suggestion/Suggestion.ts: -------------------------------------------------------------------------------- 1 | import isEqual from "../util/isEqual" 2 | import Message from "../Message" 3 | import StringRange from "../context/StringRange" 4 | 5 | export default class Suggestion { 6 | 7 | private range: StringRange; 8 | private text: string; 9 | private tooltip: Message; 10 | 11 | public constructor (range: StringRange, text: string, tooltip: Message = null) { 12 | this.range = range; 13 | this.text = text; 14 | this.tooltip = tooltip; 15 | } 16 | 17 | public getRange(): StringRange { 18 | return this.range; 19 | } 20 | 21 | public getText(): string { 22 | return this.text; 23 | } 24 | 25 | public getTooltip(): Message { 26 | return this.tooltip; 27 | } 28 | 29 | public apply(input: string): string { 30 | if (this.range.getStart() === 0 && this.range.getEnd() == input.length) { 31 | return this.text; 32 | } 33 | 34 | let result = ""; 35 | if (this.range.getStart() > 0) { 36 | result += input.substring(0, this.range.getStart()); 37 | } 38 | 39 | result += this.text; 40 | if (this.range.getEnd() < input.length) { 41 | result += input.substring(this.range.getEnd()); 42 | } 43 | 44 | return result; 45 | } 46 | 47 | public equals(o: object): boolean { 48 | if (this === o) return true; 49 | if (!(o instanceof Suggestion)) return false; 50 | 51 | return isEqual(this.range, o.range) && (this.text === o.text) && isEqual(this.tooltip, o.tooltip); 52 | } 53 | 54 | public toString(): String { 55 | return "Suggestion{" + 56 | "range=" + this.range + 57 | ", text='" + this.text + '\'' + 58 | ", tooltip='" + this.tooltip + '\'' + 59 | '}'; 60 | } 61 | 62 | public compareTo(o: Suggestion): number { 63 | return this.text < o.text ? 1 : -1; 64 | } 65 | 66 | public compareToIgnoreCase(b: Suggestion): number { 67 | return this.text.toLowerCase() < b.text.toLowerCase() ? 1 : -1; 68 | } 69 | 70 | public expand(command: string, range: StringRange): Suggestion { 71 | if (range.equals(this.range)) { 72 | return this; 73 | } 74 | 75 | let result = ""; 76 | if (range.getStart() < this.range.getStart()) { 77 | result += command.substring(range.getStart(), this.range.getStart()); 78 | } 79 | 80 | result += this.text; 81 | if (range.getEnd() > this.range.getEnd()) { 82 | result += command.substring(this.range.getEnd(), range.getEnd()); 83 | } 84 | 85 | return new Suggestion(range, result, this.tooltip); 86 | } 87 | } -------------------------------------------------------------------------------- /src/lib/suggestion/SuggestionProvider.ts: -------------------------------------------------------------------------------- 1 | import Suggestions from "./Suggestions" 2 | import SuggestionsBuilder from "./SuggestionsBuilder" 3 | import CommandContext from "../context/CommandContext" 4 | 5 | export default interface SuggestionProvider { 6 | getSuggestions(context: CommandContext, builder: SuggestionsBuilder): Promise; 7 | } -------------------------------------------------------------------------------- /src/lib/suggestion/Suggestions.ts: -------------------------------------------------------------------------------- 1 | import isEqual from "../util/isEqual" 2 | import StringRange from "../context/StringRange" 3 | import Suggestion from "./Suggestion" 4 | 5 | export default class Suggestions { 6 | 7 | private static EMPTY = new Suggestions(StringRange.at(0), []); 8 | 9 | private range: StringRange; 10 | 11 | private suggestions: Array; 12 | 13 | public constructor (range: StringRange, suggestions: Array) { 14 | this.range = range; 15 | this.suggestions = suggestions; 16 | } 17 | 18 | public getRange(): StringRange { 19 | return this.range; 20 | } 21 | 22 | public getList(): Array { 23 | return this.suggestions; 24 | } 25 | 26 | public isEmpty(): boolean { 27 | return this.suggestions.length === 0; 28 | } 29 | 30 | public equals(o: object): boolean { 31 | if (this === o) return true; 32 | 33 | if (!(o instanceof Suggestions)) return false; 34 | 35 | return this.range.equals(o.range) && isEqual(this.suggestions, o.suggestions); 36 | } 37 | 38 | public toString(): String { 39 | return "Suggestions{" + 40 | "range=" + this.range + 41 | ", suggestions=" + this.suggestions + '}'; 42 | } 43 | 44 | public static empty(): Promise { 45 | return Promise.resolve(this.EMPTY); 46 | } 47 | 48 | public static merge(command: string, input: Array): Suggestions { 49 | if (input.length === 0) { 50 | return this.EMPTY; 51 | } 52 | else if (input.length === 1) { 53 | return input[0]; 54 | } 55 | 56 | const texts = []; 57 | for (let suggestions of input) { 58 | texts.push(...suggestions.getList()); 59 | } 60 | 61 | return Suggestions.create(command, texts); 62 | } 63 | 64 | public static create(command: string, suggestions: Array): Suggestions { 65 | if (suggestions.length === 0) { 66 | return this.EMPTY; 67 | } 68 | 69 | let start = Infinity; 70 | let end = -Infinity; 71 | for (let suggestion of suggestions) { 72 | start = Math.min(suggestion.getRange().getStart(), start); 73 | end = Math.max(suggestion.getRange().getEnd(), end); 74 | } 75 | 76 | let range = new StringRange(start, end); 77 | const texts = []; 78 | for (let suggestion of suggestions) { 79 | texts.push(suggestion.expand(command, range)); 80 | } 81 | 82 | const sorted = texts.sort((a, b) => a.compareToIgnoreCase(b)); 83 | return new Suggestions(range, sorted); 84 | } 85 | } -------------------------------------------------------------------------------- /src/lib/suggestion/SuggestionsBuilder.ts: -------------------------------------------------------------------------------- 1 | import Message from "../Message" 2 | import StringRange from "../context/StringRange" 3 | import Suggestion from "./Suggestion" 4 | import Suggestions from "./Suggestions" 5 | import IntegerSuggestion from "./IntegerSuggestion" 6 | 7 | export default class SuggestionsBuilder { 8 | 9 | private input: string; 10 | 11 | private start: number; 12 | 13 | private remaining: string; 14 | 15 | private result: Array = []; 16 | 17 | public constructor (input: string, start: number) { 18 | this.input = input; 19 | this.start = start; 20 | this.remaining = input.substring(start); 21 | } 22 | 23 | public getInput(): String { 24 | return this.input; 25 | } 26 | 27 | public getStart(): number { 28 | return this.start; 29 | } 30 | 31 | public getRemaining(): string { 32 | return this.remaining; 33 | } 34 | 35 | public build(): Suggestions { 36 | return Suggestions.create(this.input, this.result); 37 | } 38 | 39 | public buildPromise(): Promise { 40 | return Promise.resolve(this.build()); 41 | } 42 | 43 | public suggest(text: string | number, tooltip: Message = null): SuggestionsBuilder { 44 | 45 | if(typeof text === "number") { 46 | this.result.push(new IntegerSuggestion(StringRange.between(this.start, this.input.length), text, tooltip)); 47 | return this; 48 | } 49 | 50 | if (text === this.remaining) 51 | return this; 52 | 53 | this.result.push(new Suggestion(StringRange.between(this.start, this.input.length), text, tooltip)); 54 | return this; 55 | } 56 | 57 | public add(other: SuggestionsBuilder): SuggestionsBuilder { 58 | this.result.push(...other.result); 59 | return this; 60 | } 61 | 62 | public createOffset(start: number): SuggestionsBuilder { 63 | return new SuggestionsBuilder(this.input, this.start); 64 | } 65 | 66 | public restart(): SuggestionsBuilder { 67 | return new SuggestionsBuilder(this.input, this.start); 68 | } 69 | } -------------------------------------------------------------------------------- /src/lib/tree/ArgumentCommandNode.ts: -------------------------------------------------------------------------------- 1 | import isEqual from "../util/isEqual" 2 | import Command from "../Command" 3 | import Predicate from "../Predicate" 4 | import RedirectModifier from "../RedirectModifier" 5 | import StringReader from "../StringReader" 6 | import ArgumentType from "../arguments/ArgumentType" 7 | import RequiredArgumentBuilder from "../builder/RequiredArgumentBuilder" 8 | import CommandContext from "../context/CommandContext" 9 | import ParsedArgument from "../context/ParsedArgument" 10 | import CommandContextBuilder from "../context/CommandContextBuilder" 11 | import Suggestions from "../suggestion/Suggestions" 12 | import SuggestionProvider from "../suggestion/SuggestionProvider" 13 | import SuggestionsBuilder from "../suggestion/SuggestionsBuilder" 14 | import CommandNode from "./CommandNode" 15 | 16 | const USAGE_ARGUMENT_OPEN: string = "<"; 17 | const USAGE_ARGUMENT_CLOSE: string = ">"; 18 | 19 | export default class ArgumentCommandNode extends CommandNode { 20 | 21 | private name: string; 22 | private type: ArgumentType; 23 | private customSuggestions: SuggestionProvider; 24 | 25 | public constructor(name: string, type: ArgumentType, command: Command, requirement: Predicate, redirect: CommandNode, modifier: RedirectModifier, forks: boolean, customSuggestions: SuggestionProvider) { 26 | super(command, requirement, redirect, modifier, forks); 27 | this.name = name; 28 | this.type = type; 29 | this.customSuggestions = customSuggestions; 30 | } 31 | 32 | public getNodeType(): string { 33 | return "argument" 34 | } 35 | 36 | public getType(): ArgumentType { 37 | return this.type; 38 | } 39 | 40 | public getName(): string { 41 | return this.name; 42 | } 43 | 44 | public getUsageText(): string { 45 | return USAGE_ARGUMENT_OPEN + this.name + USAGE_ARGUMENT_CLOSE; 46 | } 47 | 48 | public getCustomSuggestions(): SuggestionProvider { 49 | return this.customSuggestions; 50 | } 51 | 52 | public parse(reader: StringReader, contextBuilder: CommandContextBuilder) { 53 | let start = reader.getCursor(); 54 | let result: T = this.type.parse(reader); 55 | let parsed: ParsedArgument = new ParsedArgument(start, reader.getCursor(), result); 56 | contextBuilder.withArgument(this.name, parsed); 57 | contextBuilder.withNode(this, parsed.getRange()); 58 | } 59 | 60 | public listSuggestions(context: CommandContext, builder: SuggestionsBuilder): Promise { 61 | if (this.customSuggestions == null) { 62 | if (typeof this.type.listSuggestions === "function") 63 | return this.type.listSuggestions(context, builder); 64 | else 65 | return Suggestions.empty(); 66 | } 67 | else { 68 | return this.customSuggestions.getSuggestions(context, builder); 69 | } 70 | 71 | } 72 | 73 | public createBuilder(): RequiredArgumentBuilder { 74 | let builder: RequiredArgumentBuilder = RequiredArgumentBuilder.argument(this.name, this.type); 75 | builder.requires(this.getRequirement()); 76 | builder.forward(this.getRedirect(), this.getRedirectModifier(), this.isFork()); 77 | builder.suggests(this.customSuggestions); 78 | if (this.getCommand() != null) { 79 | builder.executes(this.getCommand()); 80 | } 81 | 82 | return builder; 83 | } 84 | 85 | public isValidInput(input: string): boolean { 86 | try { 87 | let reader: StringReader = new StringReader(input); 88 | this.type.parse(reader); 89 | return !reader.canRead() || reader.peek() == ' '; 90 | } 91 | catch (ignored) { 92 | } 93 | 94 | return false; 95 | } 96 | 97 | public equals(o: object): boolean { 98 | if (this === o) return true; 99 | if (!(o instanceof ArgumentCommandNode)) return false; 100 | 101 | if (!(this.name === o.name)) return false; 102 | if (!isEqual(this.type, o.type)) return false; 103 | 104 | return super.equals(o); 105 | } 106 | 107 | public getSortedKey(): string { 108 | return this.name; 109 | } 110 | 111 | public getExamples(): Iterable { 112 | return typeof this.type.getExamples === "function" ? this.type.getExamples() : []; 113 | } 114 | 115 | public toString(): string { 116 | return ""; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/lib/tree/CommandNode.ts: -------------------------------------------------------------------------------- 1 | import isEqual from "../util/isEqual" 2 | import Predicate from "../Predicate" 3 | import AmbiguityConsumer from "../AmbiguityConsumer" 4 | import Command from "../Command" 5 | import RedirectModifier from "../RedirectModifier" 6 | import StringReader from "../StringReader" 7 | import ArgumentBuilder from "../builder/ArgumentBuilder" 8 | import CommandContext from "../context/CommandContext" 9 | import CommandContextBuilder from "../context/CommandContextBuilder" 10 | import Suggestions from "../suggestion/Suggestions" 11 | import SuggestionsBuilder from "../suggestion/SuggestionsBuilder" 12 | 13 | abstract class CommandNode { 14 | 15 | private children: Map> = new Map(); 16 | 17 | private literals: Map = new Map(); 18 | 19 | private args: Map = new Map(); 20 | 21 | private requirement: Predicate; 22 | 23 | private redirect: CommandNode; 24 | 25 | private modifier: RedirectModifier; 26 | 27 | private forks: boolean; 28 | 29 | private command: Command; 30 | 31 | public abstract getNodeType(): string; 32 | 33 | public constructor (command: Command, requirement: Predicate, redirect: CommandNode, modifier: RedirectModifier, forks: boolean) { 34 | this.command = command; 35 | this.requirement = requirement || (() => true); 36 | this.redirect = redirect; 37 | this.modifier = modifier; 38 | this.forks = forks; 39 | } 40 | 41 | public getCommand(): Command { 42 | return this.command; 43 | } 44 | 45 | public getChildren(): IterableIterator> { 46 | return this.children.values(); 47 | } 48 | 49 | public getChildrenCount(): number { 50 | return this.children.size; 51 | } 52 | 53 | public getChild(name: string): CommandNode { 54 | return this.children.get(name); 55 | } 56 | 57 | public getRedirect(): CommandNode { 58 | return this.redirect; 59 | } 60 | 61 | public getRedirectModifier(): RedirectModifier { 62 | return this.modifier; 63 | } 64 | 65 | public canUse(source: S): boolean { 66 | return this.requirement(source); 67 | } 68 | 69 | public addChild(node: CommandNode) { 70 | if (node.getNodeType() === "root") { 71 | throw new Error("Cannot add a RootCommandNode as a child to any other CommandNode"); 72 | } 73 | 74 | let child= this.children.get(node.getName()); 75 | if (child != null) { 76 | // We've found something to merge onto 77 | if ((node.getCommand() != null)) { 78 | child.command = node.getCommand(); 79 | } 80 | 81 | for (let grandchild of node.getChildren()) { 82 | child.addChild(grandchild); 83 | } 84 | 85 | } 86 | else { 87 | this.children.set(node.getName(), node); 88 | if (node.getNodeType() === "literal") { 89 | this.literals.set(node.getName(), node); 90 | } 91 | else if (node.getNodeType() === "argument") { 92 | this.args.set(node.getName(), node); 93 | } 94 | 95 | } 96 | 97 | this.children = new Map([...this.children.entries()].sort((a, b) => a[1].compareTo(b[1]))) 98 | } 99 | 100 | public findAmbiguities(consumer: AmbiguityConsumer) { 101 | let matches: Array = []; 102 | for (let child of this.children.values()) { 103 | for (let sibling of this.children.values()) { 104 | if (child === sibling) { 105 | continue; 106 | } 107 | 108 | for (let input of child.getExamples()) { 109 | if (sibling.isValidInput(input)) { 110 | matches.push(input); 111 | } 112 | } 113 | 114 | if (matches.length > 0) { 115 | consumer.ambiguous(this, child, sibling, matches); 116 | matches = []; 117 | } 118 | 119 | } 120 | 121 | child.findAmbiguities(consumer); 122 | } 123 | 124 | } 125 | 126 | public abstract isValidInput(input: string): boolean; 127 | 128 | public equals(o: object): boolean { 129 | if (this === o) return true; 130 | if (!(o instanceof CommandNode)) return false; 131 | 132 | if (this.children.size !== o.children.size) { 133 | return false; 134 | } 135 | 136 | if (!isEqual(this.children, o.children)) return false; 137 | 138 | if (this.command != null ? !isEqual(this.command, o.command) : o.command != null) 139 | return false; 140 | 141 | return true; 142 | } 143 | 144 | public getRequirement() { 145 | return this.requirement; 146 | } 147 | 148 | public abstract getName(): string; 149 | 150 | public abstract getUsageText(): string; 151 | 152 | public abstract parse(reader: StringReader, contextBuilder: CommandContextBuilder): void; 153 | 154 | public abstract listSuggestions(context: CommandContext, builder: SuggestionsBuilder): Promise; 155 | 156 | public abstract createBuilder(): ArgumentBuilder; 157 | 158 | public abstract getSortedKey(): string; 159 | 160 | public getRelevantNodes(input: StringReader): Iterable> { 161 | if (this.literals.size > 0) { 162 | let cursor: number = input.getCursor(); 163 | while ((input.canRead() 164 | && (input.peek() != ' '))) { 165 | input.skip(); 166 | } 167 | 168 | let text = input.getString().substring(cursor, input.getCursor()); 169 | input.setCursor(cursor); 170 | let literal = this.literals.get(text); 171 | if (literal != null) { 172 | return[ literal ]; 173 | } 174 | else { 175 | return this.args.values(); 176 | } 177 | 178 | } 179 | else { 180 | return this.args.values(); 181 | } 182 | 183 | } 184 | 185 | public compareTo(o: CommandNode): number { 186 | if (this.getNodeType() === o.getNodeType()) { 187 | return this.getSortedKey() > o.getSortedKey() ? 1 : -1; 188 | } 189 | 190 | return (o.getNodeType() === "literal") ? 1 : -1; 191 | } 192 | 193 | public isFork(): boolean { 194 | return this.forks; 195 | } 196 | 197 | public abstract getExamples(): Iterable; 198 | } 199 | 200 | export default CommandNode; -------------------------------------------------------------------------------- /src/lib/tree/LiteralCommandNode.ts: -------------------------------------------------------------------------------- 1 | import Command from "../Command" 2 | import CommandNode from "./CommandNode" 3 | import Predicate from "../Predicate" 4 | import RedirectModifier from "../RedirectModifier" 5 | import StringReader from "../StringReader" 6 | import LiteralArgumentBuilder from "../builder/LiteralArgumentBuilder" 7 | import CommandContext from "../context/CommandContext" 8 | import CommandContextBuilder from "../context/CommandContextBuilder" 9 | import StringRange from "../context/StringRange" 10 | import CommandSyntaxException from "../exceptions/CommandSyntaxException" 11 | import Suggestions from "../suggestion/Suggestions" 12 | import SuggestionsBuilder from "../suggestion/SuggestionsBuilder" 13 | 14 | export default class LiteralCommandNode extends CommandNode { 15 | 16 | private literal: string; 17 | 18 | public constructor(literal: string, command: Command, requirement: Predicate, redirect: CommandNode, modifier: RedirectModifier, forks: boolean) { 19 | super(command, requirement, redirect, modifier, forks); 20 | this.literal = literal; 21 | } 22 | 23 | public getNodeType(): string { 24 | return "literal" 25 | } 26 | 27 | public getLiteral(): string { 28 | return this.literal; 29 | } 30 | 31 | public getName(): string { 32 | return this.literal; 33 | } 34 | 35 | public parse(reader: StringReader, contextBuilder: CommandContextBuilder) { 36 | let start = reader.getCursor(); 37 | let end = this.__parse(reader); 38 | if (end > -1) { 39 | contextBuilder.withNode(this, StringRange.between(start, end)); 40 | return; 41 | } 42 | 43 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect().createWithContext(reader, this.literal); 44 | } 45 | 46 | private __parse(reader: StringReader): number { 47 | let start = reader.getCursor(); 48 | if (reader.canRead(this.literal.length)) { 49 | let end = start + this.literal.length; 50 | if (reader.getString().substring(start, end) === this.literal) { 51 | reader.setCursor(end); 52 | if (!reader.canRead() || reader.peek() == ' ') { 53 | return end; 54 | } 55 | else { 56 | reader.setCursor(start); 57 | } 58 | } 59 | } 60 | return -1; 61 | } 62 | 63 | public listSuggestions(context: CommandContext, builder: SuggestionsBuilder): Promise { 64 | if (this.literal.toLowerCase().startsWith(builder.getRemaining().toLowerCase())) { 65 | return builder.suggest(this.literal).buildPromise(); 66 | } 67 | else { 68 | return Suggestions.empty(); 69 | } 70 | 71 | } 72 | 73 | public isValidInput(input: string): boolean { 74 | return this.__parse(new StringReader(input)) > -1; 75 | } 76 | 77 | public equals(o: object): boolean { 78 | if (this === o) return true; 79 | if (!(o instanceof LiteralCommandNode)) return false; 80 | 81 | if (!(this.literal === o.literal)) return false; 82 | 83 | return super.equals(o); 84 | } 85 | 86 | public getUsageText(): string { 87 | return this.literal; 88 | } 89 | 90 | public createBuilder(): LiteralArgumentBuilder { 91 | let builder: LiteralArgumentBuilder = LiteralArgumentBuilder.literal(this.literal); 92 | builder.requires(this.getRequirement()); 93 | builder.forward(this.getRedirect(), this.getRedirectModifier(), this.isFork()); 94 | if (this.getCommand() != null) 95 | builder.executes(this.getCommand()); 96 | 97 | return builder; 98 | } 99 | 100 | public getSortedKey(): string { 101 | return this.literal; 102 | } 103 | 104 | public getExamples(): Iterable { 105 | return [ this.literal ]; 106 | } 107 | 108 | public toString(): string { 109 | return ""; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/lib/tree/RootCommandNode.ts: -------------------------------------------------------------------------------- 1 | import CommandNode from "./CommandNode" 2 | import StringReader from "../StringReader" 3 | import ArgumentBuilder from "../builder/ArgumentBuilder" 4 | import CommandContext from "../context/CommandContext" 5 | import CommandContextBuilder from "../context/CommandContextBuilder" 6 | import Suggestions from "../suggestion/Suggestions" 7 | import SuggestionsBuilder from "../suggestion/SuggestionsBuilder" 8 | 9 | export default class RootCommandNode extends CommandNode { 10 | 11 | public constructor () { 12 | super(null, s => true, null, (s: CommandContext) => s.getSource(), false); 13 | } 14 | 15 | public getNodeType(): string { 16 | return "root" 17 | } 18 | 19 | public getName(): string { 20 | return ""; 21 | } 22 | 23 | public getUsageText(): string { 24 | return ""; 25 | } 26 | 27 | public parse(reader: StringReader, contextBuilder: CommandContextBuilder) { 28 | 29 | } 30 | 31 | public listSuggestions(context: CommandContext, builder: SuggestionsBuilder): Promise { 32 | return Suggestions.empty(); 33 | } 34 | 35 | public isValidInput(input: String): boolean { 36 | return false; 37 | } 38 | 39 | public equals(o: object): boolean { 40 | 41 | if (this === o) return true; 42 | if (!(o instanceof RootCommandNode)) return false; 43 | 44 | return super.equals(o); 45 | } 46 | 47 | public createBuilder(): ArgumentBuilder { 48 | throw new Error("Cannot convert root into a builder"); 49 | } 50 | 51 | public getSortedKey(): string { 52 | return ""; 53 | } 54 | 55 | public getExamples(): Iterable { 56 | return []; 57 | } 58 | 59 | public toString(): string { 60 | return ""; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/lib/util/isEqual.ts: -------------------------------------------------------------------------------- 1 | export default function isEqual(a: any, b: any): boolean { // @Warning: May cause an infinite loop 2 | 3 | if (a === b) 4 | return true; 5 | 6 | if (typeof a != typeof b) 7 | return false; 8 | 9 | if (!(a instanceof Object)) 10 | return false; 11 | 12 | if (typeof a === "function") 13 | return a.toString() === b.toString(); 14 | 15 | if (a.constructor !== b.constructor) 16 | return false; 17 | 18 | if (a instanceof Map) 19 | return isMapEqual(a, b); 20 | 21 | if (a instanceof Set) 22 | return isArrayEqual([...a], [...b]); 23 | 24 | if (a instanceof Array) 25 | return isArrayEqual(a, b); 26 | 27 | if (typeof a === "object") 28 | return isObjectEqual(a, b); 29 | 30 | return false; 31 | } 32 | 33 | function isMapEqual(a: Map, b: Map): boolean { 34 | 35 | if (a.size != b.size) return false; 36 | 37 | for (let [key, val] of a) { 38 | const testVal = b.get(key); 39 | if (!isEqual(testVal, val)) 40 | return false; 41 | if (testVal === undefined && !b.has(key)) 42 | return false; 43 | } 44 | 45 | return true; 46 | } 47 | 48 | function isArrayEqual(a: Array, b: Array): boolean { 49 | 50 | if (a.length != b.length) return false; 51 | 52 | for(let i = 0; i < a.length; i++) 53 | if (!isEqual(a[i], b[i])) 54 | return false; 55 | 56 | return true; 57 | } 58 | 59 | function isObjectEqual(a: TypicalObject, b: TypicalObject): boolean { 60 | 61 | const aKeys = Object.keys(a); 62 | const bKeys = Object.keys(b); 63 | 64 | if (aKeys.length != bKeys.length) return false; 65 | if (!aKeys.every(key => b.hasOwnProperty(key))) return false; 66 | 67 | return aKeys.every((key: string) => { 68 | return isEqual(a[key], b[key]) 69 | }); 70 | } 71 | 72 | interface TypicalObject { 73 | [ index: string ]: any; 74 | [ index: number ]: any; 75 | }; -------------------------------------------------------------------------------- /test/CommandDispatcherUsagesTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai" 2 | import Command from "../src/lib/Command" 3 | import CommandDispatcher from "../src/lib/CommandDispatcher" 4 | import { literal } from "../src/lib/builder/LiteralArgumentBuilder" 5 | import StringReader from "../src/lib/StringReader"; 6 | 7 | describe('CommandDispatcher Usage Test', () => { 8 | 9 | let subject: CommandDispatcher; 10 | const source: Object = {}; 11 | const command: Command = () => 0; 12 | beforeEach(() => { 13 | subject = new CommandDispatcher(); 14 | subject.register( 15 | literal("a") 16 | .then( 17 | literal("1") 18 | .then(literal("i").executes(command)) 19 | .then(literal("ii").executes(command)) 20 | ) 21 | .then( 22 | literal("2") 23 | .then(literal("i").executes(command)) 24 | .then(literal("ii").executes(command)) 25 | ) 26 | ); 27 | subject.register(literal("b").then(literal("1").executes(command))); 28 | subject.register(literal("c").executes(command)); 29 | subject.register(literal("d").requires(s => false).executes(command)); 30 | subject.register( 31 | literal("e") 32 | .executes(command) 33 | .then( 34 | literal("1") 35 | .executes(command) 36 | .then(literal("i").executes(command)) 37 | .then(literal("ii").executes(command)) 38 | ) 39 | ); 40 | subject.register( 41 | literal("f") 42 | .then( 43 | literal("1") 44 | .then(literal("i").executes(command)) 45 | .then(literal("ii").executes(command).requires(s => false)) 46 | ) 47 | .then( 48 | literal("2") 49 | .then(literal("i").executes(command).requires(s => false)) 50 | .then(literal("ii").executes(command)) 51 | ) 52 | ); 53 | subject.register( 54 | literal("g") 55 | .executes(command) 56 | .then(literal("1").then(literal("i").executes(command))) 57 | ); 58 | subject.register( 59 | literal("h") 60 | .executes(command) 61 | .then(literal("1").then(literal("i").executes(command))) 62 | .then(literal("2").then(literal("i").then(literal("ii").executes(command)))) 63 | .then(literal("3").executes(command)) 64 | ); 65 | subject.register( 66 | literal("i") 67 | .executes(command) 68 | .then(literal("1").executes(command)) 69 | .then(literal("2").executes(command)) 70 | ); 71 | subject.register( 72 | literal("j") 73 | .redirect(subject.getRoot()) 74 | ); 75 | subject.register( 76 | literal("k") 77 | .redirect(get("h")) 78 | ); 79 | }) 80 | 81 | function get(command: string | StringReader) { 82 | const t = subject.parse(command, source).getContext().getNodes(); 83 | return t[t.length - 1].getNode(); 84 | } 85 | 86 | it('testAllUsage_noCommands', () => { 87 | subject = new CommandDispatcher(); 88 | const results = subject.getAllUsage(subject.getRoot(), source, true); 89 | assert.equal([...results.entries()].length, 0); 90 | }) 91 | 92 | it('testSmartUsage_noCommands', () => { 93 | subject = new CommandDispatcher(); 94 | const results = subject.getSmartUsage(subject.getRoot(), source); 95 | assert.equal([...results.entries()].length, 0); 96 | }) 97 | 98 | it('testAllUsage_root', () => { 99 | const results = subject.getAllUsage(subject.getRoot(), source, true); 100 | expect(results).to.deep.equal([ 101 | "a 1 i", 102 | "a 1 ii", 103 | "a 2 i", 104 | "a 2 ii", 105 | "b 1", 106 | "c", 107 | "e", 108 | "e 1", 109 | "e 1 i", 110 | "e 1 ii", 111 | "f 1 i", 112 | "f 2 ii", 113 | "g", 114 | "g 1 i", 115 | "h", 116 | "h 1 i", 117 | "h 2 i ii", 118 | "h 3", 119 | "i", 120 | "i 1", 121 | "i 2", 122 | "j ...", 123 | "k -> h", 124 | ]); 125 | }) 126 | 127 | it('testSmartUsage_root', () => { 128 | const results = subject.getSmartUsage(subject.getRoot(), source); 129 | expect(results).to.deep.equal( 130 | new Map().set(get("a"), "a (1|2)") 131 | .set(get("b"), "b 1") 132 | .set(get("c"), "c") 133 | .set(get("e"), "e [1]") 134 | .set(get("f"), "f (1|2)") 135 | .set(get("g"), "g [1]") 136 | .set(get("h"), "h [1|2|3]") 137 | .set(get("i"), "i [1|2]") 138 | .set(get("j"), "j ...") 139 | .set(get("k"), "k -> h") 140 | ); 141 | }) 142 | 143 | it('testSmartUsage_h', () => { 144 | const results = subject.getSmartUsage(get("h"), source); 145 | expect(results).to.deep.equal(new Map() 146 | .set(get("h 1"), "[1] i") 147 | .set(get("h 2"), "[2] i ii") 148 | .set(get("h 3"), "[3]") 149 | ); 150 | }) 151 | 152 | it('testSmartUsage_offsetH', () => { 153 | const offsetH = new StringReader("/|/|/h"); 154 | offsetH.setCursor(5); 155 | 156 | const results = subject.getSmartUsage(get(offsetH), source); 157 | expect(results).to.deep.equal(new Map() 158 | .set(get("h 1"), "[1] i") 159 | .set(get("h 2"), "[2] i ii") 160 | .set(get("h 3"), "[3]") 161 | ); 162 | }) 163 | }) -------------------------------------------------------------------------------- /test/arguments/BoolArgumentTypeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import { mock, instance, when, verify } from 'ts-mockito' 3 | import { DefaultType } from "../../src/lib/arguments/ArgumentType" 4 | import StringReader from "../../src/lib/StringReader" 5 | 6 | describe('BoolArgumentTypeTest', () => { 7 | const type = DefaultType.bool(); 8 | 9 | it('parse', () => { 10 | const mockedReader = mock(StringReader); 11 | when(mockedReader.readBoolean()).thenReturn(true); 12 | 13 | const reader = instance(mockedReader); 14 | assert.equal(type.parse(reader), true); 15 | 16 | verify(mockedReader.readBoolean()).once(); 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/arguments/FloatArgumentTypeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from 'chai' 2 | import testEquality from "../utils/testEquality" 3 | import CommandSyntaxException from "../../src/lib/exceptions/CommandSyntaxException" 4 | import { DefaultType } from "../../src/lib/arguments/ArgumentType" 5 | import StringReader from '../../src/lib/StringReader'; 6 | 7 | const {float} = DefaultType; 8 | 9 | describe('floatumentTypeTest', () => { 10 | let type; 11 | 12 | beforeEach(() => { 13 | type = float(-100, 100); 14 | }) 15 | 16 | it('parse', () => { 17 | const reader = new StringReader("15"); 18 | assert.equal(float().parse(reader), 15); 19 | assert.equal(reader.canRead(), false); 20 | }) 21 | 22 | it('parse_tooSmall', done => { 23 | const reader = new StringReader("-5"); 24 | try { 25 | float(0, 100).parse(reader); 26 | } catch (ex) { 27 | expect(ex.getType().toString()).to.equal(CommandSyntaxException.BUILT_IN_EXCEPTIONS.floatTooLow().toString()); 28 | assert.equal(ex.getCursor(), 0); 29 | done(); 30 | return; 31 | } 32 | assert.fail(); 33 | }) 34 | 35 | it('parse_tooBig', done => { 36 | const reader = new StringReader("5"); 37 | try { 38 | float(-100, 0).parse(reader); 39 | } catch (ex) { 40 | expect(ex.getType().toString()).to.equal(CommandSyntaxException.BUILT_IN_EXCEPTIONS.floatTooHigh().toString()); 41 | assert.equal(ex.getCursor(), 0); 42 | done(); 43 | return; 44 | } 45 | assert.fail(); 46 | }) 47 | 48 | it('testEquals', () => { 49 | testEquality(float(), float()) 50 | testEquality(float(-100, 100), float(-100, 100)) 51 | testEquality(float(-100, 50), float(-100, 50)) 52 | testEquality(float(-50, 100), float(-50, 100)) 53 | }) 54 | 55 | it('testToString', () => { 56 | assert.equal(float()+ "", "float()"); 57 | assert.equal(float(-100)+ "", "float(-100)"); 58 | assert.equal(float(-100, 100)+ "", "float(-100, 100)"); 59 | assert.equal(float(Number.MIN_SAFE_INTEGER, 100)+ "", "float(-9007199254740991, 100)"); 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /test/arguments/IntegerArgumentTypeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from 'chai' 2 | import testEquality from "../utils/testEquality" 3 | import CommandSyntaxException from "../../src/lib/exceptions/CommandSyntaxException" 4 | import { DefaultType } from "../../src/lib/arguments/ArgumentType" 5 | import StringReader from '../../src/lib/StringReader'; 6 | 7 | const { integer } = DefaultType; 8 | 9 | describe('integerumentTypeTest', () => { 10 | let type; 11 | 12 | beforeEach(() => { 13 | type = integer(-100, 100); 14 | }) 15 | 16 | it('parse', () => { 17 | const reader = new StringReader("15"); 18 | assert.equal(integer().parse(reader), 15); 19 | assert.equal(reader.canRead(), false); 20 | }) 21 | 22 | it('parse_tooSmall', done => { 23 | const reader = new StringReader("-5"); 24 | try { 25 | integer(0, 100).parse(reader); 26 | } catch (ex) { 27 | expect(ex.getType().toString()).to.equal(CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooLow().toString()); 28 | assert.equal(ex.getCursor(), 0); 29 | done(); 30 | return; 31 | } 32 | 33 | assert.fail(); 34 | }) 35 | 36 | it('parse_tooBig', done => { 37 | const reader = new StringReader("5"); 38 | try { 39 | integer(-100, 0).parse(reader); 40 | } catch (ex) { 41 | expect(ex.getType().toString()).to.equal(CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooHigh().toString()); 42 | assert.equal(ex.getCursor(), 0); 43 | done(); 44 | return; 45 | } 46 | 47 | assert.fail(); 48 | }) 49 | 50 | it('testEquals', () => { 51 | testEquality(integer(), integer()) 52 | testEquality(integer(-100, 100), integer(-100, 100)) 53 | testEquality(integer(-100, 50), integer(-100, 50)) 54 | testEquality(integer(-50, 100), integer(-50, 100)) 55 | }) 56 | 57 | it('testToString', () => { 58 | assert.equal(integer()+ "", "integer()"); 59 | assert.equal(integer(-100)+ "", "integer(-100)"); 60 | assert.equal(integer(-100, 100)+ "", "integer(-100, 100)"); 61 | assert.equal(integer(Number.MIN_SAFE_INTEGER, 100)+ "", "integer(-9007199254740991, 100)"); 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /test/arguments/StringArgumentTypeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import { mock, instance, when, verify, anything } from 'ts-mockito' 3 | import { DefaultType } from "../../src/lib/arguments/ArgumentType" 4 | import StringReader from '../../src/lib/StringReader'; 5 | import StringArgumentType from "../../src/lib/arguments/StringArgumentType" 6 | 7 | const { word, string, greedyString } = DefaultType 8 | const { escapeIfRequired } = StringArgumentType 9 | 10 | describe('StringArgumentTypeTest', () => { 11 | 12 | it('testParseWord', () => { 13 | const mockedReader = mock(StringReader); 14 | when(mockedReader.readUnquotedString()).thenReturn("hello"); 15 | const reader = instance(mockedReader) 16 | 17 | assert.equal(word().parse(reader), "hello"); 18 | 19 | verify(mockedReader.readUnquotedString()).called(); 20 | }) 21 | 22 | it('testParseString', () => { 23 | const mockedReader = mock(StringReader); 24 | when(mockedReader.readString()).thenReturn("hello world"); 25 | const reader = instance(mockedReader) 26 | 27 | assert.equal(string().parse(reader), "hello world"); 28 | verify(mockedReader.readString()).called(); 29 | }) 30 | 31 | it('testParseGreedyString', () => { 32 | const reader = new StringReader("Hello world! This is a test."); 33 | assert.equal(greedyString().parse(reader), "Hello world! This is a test."); 34 | assert.equal(reader.canRead(), false); 35 | }) 36 | 37 | it('testToString', () => { 38 | assert.equal(string() + "", "string()"); 39 | }) 40 | 41 | it('testEscapeIfRequired_notRequired', () => { 42 | assert.equal(escapeIfRequired("hello"), "hello"); 43 | assert.equal(escapeIfRequired(""), ""); 44 | }) 45 | 46 | it('testEscapeIfRequired_multipleWords', () => { 47 | assert.equal(escapeIfRequired("hello world"), "\"hello world\""); 48 | }) 49 | 50 | it('testEscapeIfRequired_quote', () => { 51 | assert.equal(escapeIfRequired("hello \"world\"!"), "\"hello \\\"world\\\"!\""); 52 | }) 53 | 54 | it('testEscapeIfRequired_escapes', () => { 55 | assert.equal(escapeIfRequired("\\"), "\"\\\\\""); 56 | }) 57 | 58 | it('testEscapeIfRequired_singleQuote', () => { 59 | assert.equal(escapeIfRequired("\""), "\"\\\"\""); 60 | }) 61 | }) -------------------------------------------------------------------------------- /test/benchmarks/ExecuteBenchmark.js: -------------------------------------------------------------------------------- 1 | let output = "" 2 | const outputStream = {write(content) { output = content; console.log(content) }} 3 | 4 | const { literal, CommandDispatcher } = require("../../dist") 5 | 6 | const dispatcher = new CommandDispatcher(); 7 | dispatcher.register(literal("command").executes(() => 0)); 8 | dispatcher.register(literal("redirect").redirect(dispatcher.getRoot())); 9 | dispatcher.register(literal("fork").fork(dispatcher.getRoot(), () => [{}, {}, {}])); 10 | const simple = dispatcher.parse("command", new Object()); 11 | const singleRedirect = dispatcher.parse("redirect command", new Object()); 12 | const forkedRedirect = dispatcher.parse("fork command", new Object()); 13 | 14 | bench( 15 | [ 16 | { 17 | label: "execute simple", 18 | fn() { dispatcher.execute(simple) } 19 | }, 20 | { 21 | label: "execute single redirect", 22 | fn() { dispatcher.execute(singleRedirect) } 23 | }, 24 | { 25 | label: "execute forked redirect", 26 | fn() { dispatcher.execute(forkedRedirect) } 27 | }, 28 | ], 29 | { stream: outputStream, runs: 1000 } 30 | ) 31 | 32 | const d = new Date(); 33 | require('fs').writeFileSync(`./BenchmarkResult/Execute-${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}-${d.getHours()}-${d.getMinutes()}.txt`, output.replace(/\[1m/g, '').replace(/\[0m/g, '')) -------------------------------------------------------------------------------- /test/benchmarks/ParsingBenchmark.js: -------------------------------------------------------------------------------- 1 | let output = "" 2 | const outputStream = {write(content) { output = content; console.log(content) }} 3 | 4 | const { literal, CommandDispatcher } = require("../../dist") 5 | 6 | const subject = new CommandDispatcher(); 7 | subject.register( 8 | literal("a") 9 | .then( 10 | literal("1") 11 | .then(literal("i").executes(() => 0)) 12 | .then(literal("ii").executes(() => 0)) 13 | ) 14 | .then( 15 | literal("2") 16 | .then(literal("i").executes(() => 0)) 17 | .then(literal("ii").executes(() => 0)) 18 | ) 19 | ); 20 | subject.register(literal("b").then(literal("1").executes(() => 0))); 21 | subject.register(literal("c").executes(() => 0)); 22 | subject.register(literal("d").requires(s => false).executes(() => 0)); 23 | subject.register( 24 | literal("e") 25 | .executes(() => 0) 26 | .then( 27 | literal("1") 28 | .executes(() => 0) 29 | .then(literal("i").executes(() => 0)) 30 | .then(literal("ii").executes(() => 0)) 31 | ) 32 | ); 33 | subject.register( 34 | literal("f") 35 | .then( 36 | literal("1") 37 | .then(literal("i").executes(() => 0)) 38 | .then(literal("ii").executes(() => 0).requires(s => false)) 39 | ) 40 | .then( 41 | literal("2") 42 | .then(literal("i").executes(() => 0).requires(s => false)) 43 | .then(literal("ii").executes(() => 0)) 44 | ) 45 | ); 46 | subject.register( 47 | literal("g") 48 | .executes(() => 0) 49 | .then(literal("1").then(literal("i").executes(() => 0))) 50 | ); 51 | const h = subject.register( 52 | literal("h") 53 | .executes(() => 0) 54 | .then(literal("1").then(literal("i").executes(() => 0))) 55 | .then(literal("2").then(literal("i").then(literal("ii").executes(() => 0)))) 56 | .then(literal("3").executes(() => 0)) 57 | ); 58 | subject.register( 59 | literal("i") 60 | .executes(() => 0) 61 | .then(literal("1").executes(() => 0)) 62 | .then(literal("2").executes(() => 0)) 63 | ); 64 | subject.register( 65 | literal("j") 66 | .redirect(subject.getRoot()) 67 | ); 68 | subject.register( 69 | literal("k") 70 | .redirect(h) 71 | ); 72 | 73 | bench( 74 | [ 75 | { 76 | label: "parse a 1 i", 77 | fn() { subject.parse("a 1 i", new Object()); } 78 | }, 79 | { 80 | label: "parse c", 81 | fn() { subject.parse("c", new Object()); } 82 | }, 83 | { 84 | label: "parse k 1 i", 85 | fn() { subject.parse("k 1 i", new Object()); } 86 | }, 87 | { 88 | label: "parse _", 89 | fn() { subject.parse("c", new Object()); } 90 | } 91 | ], 92 | { stream: outputStream, runs: 1000 } 93 | ) 94 | 95 | const d = new Date(); 96 | require('fs').writeFileSync(`./BenchmarkResult/Parsing-${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}-${d.getHours()}-${d.getMinutes()}.txt`, output.replace(/\[1m/g, '').replace(/\[0m/g, '')) -------------------------------------------------------------------------------- /test/builder/ArgumentBuilderTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai" 2 | import { mock, instance } from "ts-mockito" 3 | import CommandNode from "../../src/lib/tree/CommandNode" 4 | import ArgumentBuilder from "../../src/lib/builder/ArgumentBuilder"; 5 | import { argument } from "../../src/lib/builder/RequiredArgumentBuilder" 6 | import { literal } from "../../src/lib/builder/LiteralArgumentBuilder" 7 | import { DefaultType } from "../../src/lib/arguments/ArgumentType" 8 | 9 | const { integer } = DefaultType; 10 | 11 | class TestableArgumentBuilder extends ArgumentBuilder> { 12 | public getThis(): TestableArgumentBuilder { 13 | return this; 14 | } 15 | public build(): CommandNode { 16 | return null; 17 | } 18 | } 19 | 20 | describe('ArgumentBuilderTest', () => { 21 | let builder: TestableArgumentBuilder; 22 | 23 | beforeEach(() => { 24 | builder = new TestableArgumentBuilder(); 25 | }) 26 | 27 | it('testArguments', () => { 28 | const arg = argument("bar", integer()); 29 | 30 | builder.then(arg); 31 | 32 | assert.equal([...builder.getArguments()].length, 1); 33 | expect([...builder.getArguments()][0].equals(arg.build())).to.equal(true); 34 | }) 35 | 36 | it('testRedirect', () => { 37 | const target = instance(mock(CommandNode)); 38 | builder.redirect(target); 39 | assert.deepEqual(builder.getRedirect(), target); 40 | }) 41 | 42 | it('testRedirect_withChild', () => { 43 | try { 44 | const target = instance(mock(CommandNode)); 45 | builder.then(literal("foo")); 46 | builder.redirect(target); 47 | assert.fail(); 48 | } catch (ignore) { 49 | } 50 | }) 51 | 52 | it('testThen_withRedirect', () => { 53 | try { 54 | const target = instance(mock(CommandNode)); 55 | builder.redirect(target); 56 | builder.then(literal("foo")); 57 | assert.fail(); 58 | } catch (ignore) { 59 | } 60 | }) 61 | }) -------------------------------------------------------------------------------- /test/builder/LiteralArgumentBuilderTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from 'chai' 2 | import Command from "../../src/lib/Command" 3 | import LiteralArgumentBuilder from "../../src/lib/builder/LiteralArgumentBuilder" 4 | import { argument } from "../../src/lib/builder/RequiredArgumentBuilder" 5 | import { DefaultType } from "../../src/lib/arguments/ArgumentType" 6 | 7 | const { integer } = DefaultType; 8 | 9 | describe('LiteralArgumentBuilderTest', () => { 10 | 11 | let builder: LiteralArgumentBuilder; 12 | const command: Command = () => 0; 13 | 14 | beforeEach(() => { 15 | builder = new LiteralArgumentBuilder("foo"); 16 | }) 17 | 18 | it('testBuild', () => { 19 | const node = builder.build(); 20 | assert.equal(node.getLiteral(), "foo"); 21 | }) 22 | 23 | it('testBuildWithExecutor', () => { 24 | const node = builder.executes(command).build(); 25 | 26 | assert.equal(node.getLiteral(), "foo"); 27 | assert.equal(node.getCommand(), command); 28 | }) 29 | 30 | it('testBuildWithChildren', () => { 31 | builder.then(argument("bar", integer())); 32 | builder.then(argument("baz", integer())); 33 | const node = builder.build(); 34 | 35 | assert.equal(node.getChildrenCount(), 2); 36 | }) 37 | }) -------------------------------------------------------------------------------- /test/builder/RequiredArgumentBuilderTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from 'chai' 2 | import { instance, mock } from 'ts-mockito' 3 | import Command from "../../src/lib/Command" 4 | import RequiredArgumentBuilder, { argument } from "../../src/lib/builder/RequiredArgumentBuilder" 5 | import ArgumentType from "../../src/lib/arguments/ArgumentType" 6 | import IntegerArgumentType from "../../src/lib/arguments/IntegerArgumentType" 7 | 8 | describe('LiteralArgumentBuilderTest', () => { 9 | 10 | let builder: RequiredArgumentBuilder; 11 | const type: ArgumentType = instance(mock(IntegerArgumentType)); 12 | const command: Command = () => 0; 13 | 14 | beforeEach(() => { 15 | builder = argument("foo", type); 16 | }) 17 | 18 | it('testBuild', () => { 19 | const node = builder.build(); 20 | 21 | assert.equal(node.getName(), "foo"); 22 | assert.equal(node.getType(), type); 23 | }) 24 | 25 | it('testBuildWithExecutor', () => { 26 | const node = builder.executes(command).build(); 27 | 28 | assert.equal(node.getName(), "foo"); 29 | assert.equal(node.getType(), type); 30 | assert.equal(node.getCommand(), command); 31 | }) 32 | 33 | it('testBuildWithChildren', () => { 34 | builder.then(argument("bar", type)); 35 | builder.then(argument("baz", type)); 36 | const node = builder.build(); 37 | 38 | assert.equal(node.getChildrenCount(), 2); 39 | }) 40 | }) -------------------------------------------------------------------------------- /test/context/CommandContextTest.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import { mock, instance } from 'ts-mockito' 3 | import testEquality from "../utils/testEquality" 4 | import CommandContextBuilder from '../../src/lib/context/CommandContextBuilder' 5 | import CommandDispatcher from '../../src/lib/CommandDispatcher'; 6 | import RootCommandNode from '../../src/lib/tree/RootCommandNode'; 7 | import ParsedArgument from '../../src/lib/context/ParsedArgument'; 8 | import CommandNode from '../../src/lib/tree/CommandNode'; 9 | import StringRange from '../../src/lib/context/StringRange'; 10 | 11 | describe('CommandContextTest', () => { 12 | let builder: CommandContextBuilder; 13 | const source = {}; 14 | const rootNode = new RootCommandNode(); 15 | const dispatcher = new CommandDispatcher(rootNode); 16 | 17 | beforeEach(() => { 18 | builder = new CommandContextBuilder(dispatcher, source, rootNode, 0); 19 | }) 20 | 21 | it('testGetArgument_nonexistent', done => { 22 | try { 23 | builder.build("").getArgument("foo"); 24 | } catch (ex) { 25 | done(); 26 | return; 27 | } 28 | 29 | assert.fail(); 30 | }) 31 | 32 | it('testGetArgument_noConverter', done => { 33 | try { 34 | const context = builder.withArgument("foo", new ParsedArgument(0, 1, Object.create(null))).build("123"); 35 | context.getArgument("foo", String); 36 | } catch (ex) { 37 | done(); 38 | return; 39 | } 40 | 41 | assert.fail(); 42 | }) 43 | 44 | it('testGetArgument', () => { 45 | const context = builder.withArgument("foo", new ParsedArgument(0, 1, 123)).build("123"); 46 | assert.equal(context.getArgument("foo", Number), 123); 47 | }) 48 | 49 | it('testSource', () => { 50 | assert.deepEqual(builder.build("").getSource(), source); 51 | }) 52 | 53 | it('testRootNode', () => { 54 | assert.deepEqual(builder.build("").getRootNode(), rootNode); 55 | }) 56 | 57 | it('testEquals', () => { 58 | const otherSource = new Object(); 59 | const command = () => 1; 60 | const otherCommand = () => 2; 61 | 62 | const mockedCommandNode = mock(CommandNode) 63 | const rootNode = new RootCommandNode() 64 | const otherRootNode = new RootCommandNode() 65 | const node: CommandNode = instance(mockedCommandNode) 66 | const otherNode: CommandNode = instance(mockedCommandNode) 67 | 68 | testEquality(new CommandContextBuilder(dispatcher, source, rootNode, 0).build(""), new CommandContextBuilder(dispatcher, source, rootNode, 0).build("")) 69 | testEquality(new CommandContextBuilder(dispatcher, source, otherRootNode, 0).build(""), new CommandContextBuilder(dispatcher, source, otherRootNode, 0).build("")) 70 | testEquality(new CommandContextBuilder(dispatcher, otherSource, rootNode, 0).build(""), new CommandContextBuilder(dispatcher, otherSource, rootNode, 0).build("")) 71 | testEquality(new CommandContextBuilder(dispatcher, source, rootNode, 0).withCommand(command).build(""), new CommandContextBuilder(dispatcher, source, rootNode, 0).withCommand(command).build("")) 72 | testEquality(new CommandContextBuilder(dispatcher, source, rootNode, 0).withCommand(otherCommand).build(""), new CommandContextBuilder(dispatcher, source, rootNode, 0).withCommand(otherCommand).build("")) 73 | testEquality(new CommandContextBuilder(dispatcher, source, rootNode, 0).withArgument("foo", new ParsedArgument(0, 1, 123)).build("123"), new CommandContextBuilder(dispatcher, source, rootNode, 0).withArgument("foo", new ParsedArgument(0, 1, 123)).build("123")) 74 | testEquality(new CommandContextBuilder(dispatcher, source, rootNode, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder(dispatcher, source, rootNode, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456")) 75 | testEquality(new CommandContextBuilder(dispatcher, source, rootNode, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder(dispatcher, source, rootNode, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456")) 76 | }) 77 | }) -------------------------------------------------------------------------------- /test/context/ParsedArgumentTest.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import testEquality from "../utils/testEquality" 3 | import StringReader from "../../src/lib/StringReader" 4 | import ParsedArgument from "../../src/lib/context/ParsedArgument" 5 | 6 | describe('ParsedArgumentTest', () => { 7 | 8 | it('testEquals', () => { 9 | testEquality(new ParsedArgument(0, 3, "bar"), new ParsedArgument(0, 3, "bar")) 10 | testEquality(new ParsedArgument(3, 6, "baz"), new ParsedArgument(3, 6, "baz")) 11 | testEquality(new ParsedArgument(6, 9, "baz"), new ParsedArgument(6, 9, "baz")) 12 | }) 13 | 14 | it('getRaw', () => { 15 | const reader = new StringReader("0123456789"); 16 | const argument = new ParsedArgument(2, 5, ""); 17 | assert.equal(argument.getRange().get(reader), "234"); 18 | }) 19 | }) -------------------------------------------------------------------------------- /test/exceptions/DynamicCommandSyntaxExceptionTypeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import StringReader from "../../src/lib/StringReader" 3 | import LiteralMessage from "../../src/lib/LiteralMessage" 4 | import DynamicCommandExceptionType from "../../src/lib/exceptions/DynamicCommandExceptionType" 5 | 6 | describe('DynamicCommandSyntaxExceptionTypeTest', () => { 7 | 8 | const type = new DynamicCommandExceptionType(name => new LiteralMessage("Hello, " + name + "!")); 9 | 10 | it('createWithContext', () => { 11 | const reader = new StringReader("Foo bar"); 12 | reader.setCursor(5); 13 | const exception = type.createWithContext(reader, "World"); 14 | assert.deepEqual(exception.getType(), type); 15 | assert.equal(exception.getInput(), "Foo bar"); 16 | assert.equal(exception.getCursor(), 5); 17 | }) 18 | }) -------------------------------------------------------------------------------- /test/exceptions/SimpleCommandSyntaxExceptionTypeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { mock, instance } from "ts-mockito" 3 | import StringReader from "../../src/lib/StringReader" 4 | import LiteralMessage from "../../src/lib/LiteralMessage" 5 | import CommandSyntaxException from "../../src/lib/exceptions/CommandSyntaxException" 6 | import SimpleCommandExceptionType from "../../src/lib/exceptions/SimpleCommandExceptionType" 7 | 8 | describe('SimpleCommandSyntaxExceptionTypeTest', () => { 9 | it('createWithContext', () => { 10 | const type = new SimpleCommandExceptionType(new LiteralMessage("error")); 11 | const reader = new StringReader("Foo bar"); 12 | reader.setCursor(5); 13 | const exception = type.createWithContext(reader); 14 | assert.deepEqual(exception.getType(), type); 15 | assert.equal(exception.getInput(), "Foo bar"); 16 | assert.equal(exception.getCursor(), 5); 17 | }) 18 | 19 | it('getContext_none', () => { 20 | const exception = new CommandSyntaxException({}, new LiteralMessage("error")); 21 | assert.equal(exception.getContext(), null); 22 | }) 23 | 24 | it('getContext_short', () => { 25 | const exception = new CommandSyntaxException({}, new LiteralMessage("error"), "Hello world!", 5); 26 | assert.equal(exception.getContext(), "Hello<--[HERE]"); 27 | }) 28 | 29 | it('getContext_long', () => { 30 | const exception = new CommandSyntaxException({}, new LiteralMessage("error"), "Hello world! This has an error in it. Oh dear!", 20); 31 | assert.equal(exception.getContext(), "...d! This ha<--[HERE]"); 32 | }) 33 | }) -------------------------------------------------------------------------------- /test/suggestion/SuggestionBuilderTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai" 2 | import StringRange from "../../src/lib/context/StringRange" 3 | import Suggestion from "../../src/lib/suggestion/Suggestion" 4 | import SuggestionsBuilder from "../../src/lib/suggestion/SuggestionsBuilder" 5 | 6 | describe('SuggestionsBuilderTest', () => { 7 | let builder: SuggestionsBuilder; 8 | 9 | beforeEach(() => { 10 | builder = new SuggestionsBuilder("Hello w", 6); 11 | }) 12 | 13 | it('suggest_appends', () => { 14 | const result = builder.suggest("world!").build(); 15 | assert.deepEqual(result.getList(), [new Suggestion(StringRange.between(6, 7), "world!")]); 16 | assert.deepEqual(result.getRange(), StringRange.between(6, 7)); 17 | assert.equal(result.isEmpty(), false); 18 | }) 19 | 20 | it('suggest_replaces', () => { 21 | const result = builder.suggest("everybody").build(); 22 | assert.deepEqual(result.getList(), [new Suggestion(StringRange.between(6, 7), "everybody")]); 23 | assert.deepEqual(result.getRange(), StringRange.between(6, 7)); 24 | assert.equal(result.isEmpty(), false); 25 | }) 26 | 27 | it('suggest_noop', () => { 28 | const result = builder.suggest("w").build(); 29 | assert.deepEqual(result.getList(), []); 30 | assert.deepEqual(result.isEmpty(), true); 31 | }) 32 | 33 | it('suggest_multiple', () => { 34 | const result = builder.suggest("world!").suggest("everybody").suggest("weekend").build(); 35 | expect(result.getList()).to.have.deep.members([new Suggestion(StringRange.between(6, 7), "everybody"), new Suggestion(StringRange.between(6, 7), "weekend"), new Suggestion(StringRange.between(6, 7), "world!")]); 36 | assert.deepEqual(result.getRange(), StringRange.between(6, 7)); 37 | assert.deepEqual(result.isEmpty(), false); 38 | }) 39 | 40 | it('restart', () => { 41 | builder.suggest("won't be included in restart"); 42 | const other = builder.restart(); 43 | expect(other).to.not.deep.equal(builder); 44 | assert.deepEqual(other.getInput(), builder.getInput()); 45 | assert.deepEqual(other.getStart(), builder.getStart()); 46 | assert.deepEqual(other.getRemaining(), builder.getRemaining()); 47 | }) 48 | 49 | it('sort_alphabetical', () => { 50 | const result = builder.suggest("2").suggest("4").suggest("6").suggest("8").suggest("30").suggest("32").build(); 51 | const actual = result.getList().map(v => v.getText()); 52 | expect(actual).to.have.deep.members(["2", "30", "32", "4", "6", "8"]); 53 | }) 54 | 55 | it('sort_numerical', () => { 56 | const result = builder.suggest(2).suggest(4).suggest(6).suggest(8).suggest(30).suggest(32).build(); 57 | const actual = result.getList().map(v => v.getText()); 58 | expect(actual).to.have.deep.members([ "2", "4", "6", "8", "30", "32"]); 59 | }) 60 | 61 | it('sort_mixed', () => { 62 | const result = builder.suggest("11").suggest("22").suggest("33").suggest("a").suggest("b").suggest("c").suggest(2).suggest(4).suggest(6).suggest(8).suggest(30).suggest(32).suggest("3a").suggest("a3").build(); 63 | const actual = result.getList().map(v => v.getText()); 64 | expect(actual).to.have.deep.members([ "11", "2", "22", "33", "3a", "4", "6", "8", "30", "32", "a", "a3", "b", "c" ]); 65 | }) 66 | }) -------------------------------------------------------------------------------- /test/suggestion/SuggestionTest.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import StringRange from "../../src/lib/context/StringRange" 3 | import Suggestion from "../../src/lib/suggestion/Suggestion" 4 | 5 | describe('SuggestionTest', () => { 6 | it('apply_insertation_start', () => { 7 | const suggestion = new Suggestion(StringRange.at(0), "And so I said: "); 8 | assert.equal(suggestion.apply("Hello world!"), "And so I said: Hello world!"); 9 | }) 10 | 11 | it('apply_insertation_middle', () => { 12 | const suggestion = new Suggestion(StringRange.at(6), "small "); 13 | assert.equal(suggestion.apply("Hello world!"), "Hello small world!"); 14 | }) 15 | 16 | it('apply_insertation_end', () => { 17 | const suggestion = new Suggestion(StringRange.at(5), " world!"); 18 | assert.equal(suggestion.apply("Hello"), "Hello world!"); 19 | }) 20 | 21 | it('apply_replacement_start', () => { 22 | const suggestion = new Suggestion(StringRange.between(0, 5), "Goodbye"); 23 | assert.equal(suggestion.apply("Hello world!"), "Goodbye world!"); 24 | }) 25 | 26 | it('apply_replacement_middle', () => { 27 | const suggestion = new Suggestion(StringRange.between(6, 11), "Alex"); 28 | assert.equal(suggestion.apply("Hello world!"), "Hello Alex!"); 29 | }) 30 | 31 | it('apply_replacement_end', () => { 32 | const suggestion = new Suggestion(StringRange.between(6, 12), "Creeper!"); 33 | assert.equal(suggestion.apply("Hello world!"), "Hello Creeper!"); 34 | }) 35 | 36 | it('apply_replacement_everything', () => { 37 | const suggestion = new Suggestion(StringRange.between(0, 12), "Oh dear."); 38 | assert.equal(suggestion.apply("Hello world!"), "Oh dear."); 39 | }) 40 | 41 | it('expand_unchanged', () => { 42 | const suggestion = new Suggestion(StringRange.at(1), "oo"); 43 | assert.deepEqual(suggestion.expand("f", StringRange.at(1)), suggestion); 44 | }) 45 | 46 | it('expand_left', () => { 47 | const suggestion = new Suggestion(StringRange.at(1), "oo"); 48 | assert.deepEqual(suggestion.expand("f", StringRange.between(0, 1)), new Suggestion(StringRange.between(0, 1), "foo")); 49 | }) 50 | 51 | it('expand_right', () => { 52 | const suggestion = new Suggestion(StringRange.at(0), "minecraft:"); 53 | assert.deepEqual(suggestion.expand("fish", StringRange.between(0, 4)), new Suggestion(StringRange.between(0, 4), "minecraft:fish")); 54 | }) 55 | 56 | it('expand_both', () => { 57 | const suggestion = new Suggestion(StringRange.at(11), "minecraft:"); 58 | assert.deepEqual(suggestion.expand("give Steve fish_block", StringRange.between(5, 21)), new Suggestion(StringRange.between(5, 21), "Steve minecraft:fish_block")); 59 | }) 60 | 61 | it('expand_replacement', () => { 62 | const suggestion = new Suggestion(StringRange.between(6, 11), "strangers"); 63 | assert.deepEqual(suggestion.expand("Hello world!", StringRange.between(0, 12)), new Suggestion(StringRange.between(0, 12), "Hello strangers!")); 64 | }) 65 | }) -------------------------------------------------------------------------------- /test/suggestion/SuggestionsTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai" 2 | import StringRange from "../../src/lib/context/StringRange" 3 | import Suggestion from "../../src/lib/suggestion/Suggestion" 4 | import Suggestions from "../../src/lib/suggestion/Suggestions" 5 | 6 | describe('SuggestionsTest', () => { 7 | it('merge_empty', () => { 8 | const merged = Suggestions.merge("foo b", []); 9 | assert.equal(merged.isEmpty(), true); 10 | }) 11 | 12 | it('merge_single', () => { 13 | const suggestions = new Suggestions(StringRange.at(5), [new Suggestion(StringRange.at(5), "ar")]); 14 | const merged = Suggestions.merge("foo b", [suggestions]); 15 | assert.deepEqual(merged, suggestions); 16 | }) 17 | 18 | it('merge_multiple', () => { 19 | const a = new Suggestions(StringRange.at(5), [new Suggestion(StringRange.at(5), "ar"), new Suggestion(StringRange.at(5), "az"), new Suggestion(StringRange.at(5), "Az")]); 20 | const b = new Suggestions(StringRange.between(4, 5), [new Suggestion(StringRange.between(4, 5), "foo"), new Suggestion(StringRange.between(4, 5), "qux"), new Suggestion(StringRange.between(4, 5), "apple"), new Suggestion(StringRange.between(4, 5), "Bar")]); 21 | const merged = Suggestions.merge("foo b", [a, b]); 22 | expect(merged.getList()).to.have.deep.members([ new Suggestion(StringRange.between(4, 5), "apple"), new Suggestion(StringRange.between(4, 5), "bar"), new Suggestion(StringRange.between(4, 5), "Bar"), new Suggestion(StringRange.between(4, 5), "baz"), new Suggestion(StringRange.between(4, 5), "bAz"), new Suggestion(StringRange.between(4, 5), "foo"), new Suggestion(StringRange.between(4, 5), "qux")]); 23 | }) 24 | }) -------------------------------------------------------------------------------- /test/tree/AbstractCommandNodeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai" 2 | import { mock, instance } from "ts-mockito" 3 | import Command from "../../src/lib/Command" 4 | import CommandNode from "../../src/lib/tree/CommandNode" 5 | import RootCommandNode from "../../src/lib/tree/RootCommandNode" 6 | import { literal } from "../../src/lib/builder/LiteralArgumentBuilder" 7 | 8 | describe('AbstractCommandNodeTest', () => { 9 | const command: Command = () => 0; 10 | 11 | function getCommandNode(): CommandNode { 12 | return new RootCommandNode(); 13 | } 14 | 15 | it('testAddChild', () => { 16 | const node = getCommandNode(); 17 | 18 | node.addChild(literal("child1").build()); 19 | node.addChild(literal("child2").build()); 20 | node.addChild(literal("child1").build()); 21 | 22 | assert.equal(node.getChildrenCount(), 2); 23 | }) 24 | 25 | it('testAddChildMergesGrandchildren', () => { 26 | const node = getCommandNode(); 27 | 28 | node.addChild(literal("child").then( 29 | literal("grandchild1") 30 | ).build()); 31 | 32 | node.addChild(literal("child").then( 33 | literal("grandchild2") 34 | ).build()); 35 | 36 | assert.equal(node.getChildrenCount(), 1); 37 | assert.equal(node.getChildren().next().value.getChildrenCount(), 2); 38 | }) 39 | 40 | it('testAddChildPreservesCommand', () => { 41 | const node = getCommandNode(); 42 | 43 | node.addChild(literal("child").executes(command).build()); 44 | node.addChild(literal("child").build()); 45 | 46 | expect(node.getChildren().next().value.getCommand()).to.deep.equal(command); 47 | }) 48 | 49 | it('testAddChildOverwritesCommand', () => { 50 | const node = getCommandNode(); 51 | 52 | node.addChild(literal("child").build()); 53 | node.addChild(literal("child").executes(command).build()); 54 | 55 | expect(node.getChildren().next().value.getCommand()).to.deep.equal(command); 56 | }) 57 | }) -------------------------------------------------------------------------------- /test/tree/ArgumentCommandNodeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import testEquality from "../utils/testEquality" 3 | import { DefaultType } from "../../src/lib/arguments/ArgumentType" 4 | import Command from "../../src/lib/Command" 5 | import CommandNode from "../../src/lib/tree/CommandNode" 6 | import RootCommandNode from "../../src/lib/tree/RootCommandNode" 7 | import CommandDispatcher from "../../src/lib/CommandDispatcher" 8 | import ArgumentCommandNode from "../../src/lib/tree/ArgumentCommandNode" 9 | import CommandContextBuilder from "../../src/lib/context/CommandContextBuilder" 10 | import RequiredArgumentBuilder, { argument } from "../../src/lib/builder/RequiredArgumentBuilder" 11 | import SuggestionsBuilder from "../../src/lib/suggestion/SuggestionsBuilder" 12 | import StringReader from "../../src/lib/StringReader" 13 | 14 | const { integer } = DefaultType; 15 | 16 | describe('ArgumentCommandNodeTest', () => { 17 | const command: Command = () => 0; 18 | let contextBuilder: CommandContextBuilder; 19 | let node: ArgumentCommandNode; 20 | 21 | function getCommandNode(): CommandNode { 22 | return node; 23 | } 24 | 25 | beforeEach(() => { 26 | node = argument("foo", integer()).build(); 27 | contextBuilder = new CommandContextBuilder(new CommandDispatcher(), new Object(), new RootCommandNode(), 0); 28 | }) 29 | 30 | it('testParse', () => { 31 | const reader = new StringReader("123 456"); 32 | node.parse(reader, contextBuilder); 33 | 34 | assert.equal(contextBuilder.getArguments().has("foo"), true); 35 | assert.equal(contextBuilder.getArguments().get("foo").getResult(), 123); 36 | }) 37 | 38 | it('testUsage', () => { 39 | assert.equal(node.getUsageText(), ""); 40 | }) 41 | 42 | it('testSuggestions', async () => { 43 | const result = await node.listSuggestions(contextBuilder.build(""), new SuggestionsBuilder("", 0)); 44 | assert.equal(result.isEmpty(), true); 45 | }) 46 | 47 | it('testEquals', () => { 48 | testEquality( 49 | argument("foo", integer()).build(), 50 | argument("foo", integer()).build() 51 | ) 52 | testEquality( 53 | argument("foo", integer()).executes(command).build(), 54 | argument("foo", integer()).executes(command).build() 55 | ) 56 | testEquality( 57 | argument("bar", integer(-100, 100)).build(), 58 | argument("bar", integer(-100, 100)).build() 59 | ) 60 | testEquality( 61 | argument("foo", integer(-100, 100)).build(), 62 | argument("foo", integer(-100, 100)).build() 63 | ) 64 | testEquality( 65 | argument("foo", integer()).then( 66 | argument("bar", integer()) 67 | ).build(), 68 | argument("foo", integer()).then( 69 | argument("bar", integer()) 70 | ).build() 71 | ) 72 | }) 73 | 74 | it('testCreateBuilder', () => { 75 | const builder: RequiredArgumentBuilder = node.createBuilder(); 76 | assert.equal(builder.getName(), node.getName()); 77 | assert.deepEqual(builder.getType(), node.getType()); 78 | assert.deepEqual(builder.getRequirement(), node.getRequirement()); 79 | assert.deepEqual(builder.getCommand(), node.getCommand()); 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /test/tree/LiteralCommandNodeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai" 2 | import testEquality from "../utils/testEquality" 3 | import CommandDispatcher from "../../src/lib/CommandDispatcher" 4 | import RootCommandNode from "../../src/lib/tree/RootCommandNode" 5 | import LiteralCommandNode from "../../src/lib/tree/LiteralCommandNode" 6 | import CommandContextBuilder from "../../src/lib/context/CommandContextBuilder" 7 | import CommandSyntaxException from "../../src/lib/exceptions/CommandSyntaxException" 8 | import Suggestion from "../../src/lib/suggestion/Suggestion" 9 | import SuggestionsBuilder from "../../src/lib/suggestion/SuggestionsBuilder" 10 | import { literal } from "../../src/lib/builder/LiteralArgumentBuilder" 11 | import StringReader from "../../src/lib/StringReader" 12 | import StringRange from "../../src/lib/context/StringRange" 13 | 14 | describe('LiteralCommandNodeTest', () => { 15 | let node: LiteralCommandNode; 16 | let contextBuilder: CommandContextBuilder; 17 | 18 | beforeEach(() => { 19 | node = literal("foo").build(); 20 | contextBuilder = new CommandContextBuilder(new CommandDispatcher(), new Object(), new RootCommandNode(), 0); 21 | }) 22 | 23 | it('testParse', () => { 24 | const reader = new StringReader("foo bar"); 25 | node.parse(reader, contextBuilder); 26 | assert.equal(reader.getRemaining(), " bar"); 27 | }) 28 | 29 | it('testParseExact', () => { 30 | const reader = new StringReader("foo"); 31 | node.parse(reader, contextBuilder); 32 | assert.equal(reader.getRemaining(), ""); 33 | }) 34 | 35 | it('testParseSimilar', done => { 36 | const reader = new StringReader("foobar"); 37 | try { 38 | node.parse(reader, contextBuilder); 39 | } catch (ex) { 40 | assert.equal(ex.getType(), CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect()); 41 | assert.equal(ex.getCursor(), 0); 42 | done(); 43 | return; 44 | } 45 | 46 | assert.fail(); 47 | }) 48 | 49 | it('testParseInvalid', done => { 50 | const reader = new StringReader("bar"); 51 | try { 52 | node.parse(reader, contextBuilder); 53 | } catch (ex) { 54 | assert.equal(ex.getType(), CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect()); 55 | assert.equal(ex.getCursor(), 0); 56 | done(); 57 | return; 58 | } 59 | 60 | assert.fail(); 61 | }) 62 | 63 | it('testUsage', () => { 64 | assert.equal(node.getUsageText(), "foo"); 65 | }) 66 | 67 | it('testSuggestions', async () => { 68 | const empty = await node.listSuggestions(contextBuilder.build(""), new SuggestionsBuilder("", 0)); 69 | expect(empty.getList()).to.deep.equal([new Suggestion(StringRange.at(0), "foo")]); 70 | 71 | const foo = await node.listSuggestions(contextBuilder.build("foo"), new SuggestionsBuilder("foo", 0)); 72 | assert.equal(foo.isEmpty(), true); 73 | 74 | const food = await node.listSuggestions(contextBuilder.build("food"), new SuggestionsBuilder("food", 0)); 75 | assert.equal(food.isEmpty(), true); 76 | 77 | const b = await node.listSuggestions(contextBuilder.build("b"), new SuggestionsBuilder("b", 0)); 78 | assert.equal(food.isEmpty(), true); 79 | }) 80 | 81 | it('testEquals', () => { 82 | const command = () => 0; 83 | 84 | testEquality( 85 | literal("foo").build(), 86 | literal("foo").build() 87 | ) 88 | testEquality( 89 | literal("bar").executes(command).build(), 90 | literal("bar").executes(command).build() 91 | ) 92 | testEquality( 93 | literal("bar").build(), 94 | literal("bar").build() 95 | ) 96 | testEquality( 97 | literal("foo").then( 98 | literal("bar") 99 | ).build(), 100 | literal("foo").then( 101 | literal("bar") 102 | ).build() 103 | ) 104 | }) 105 | 106 | it('testCreateBuilder', () => { 107 | const builder = node.createBuilder(); 108 | assert.deepEqual(builder.getLiteral(), node.getLiteral()); 109 | assert.deepEqual(builder.getRequirement(), node.getRequirement()); 110 | assert.deepEqual(builder.getCommand(), node.getCommand()); 111 | }) 112 | }) -------------------------------------------------------------------------------- /test/tree/RootCommandNodeTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai" 2 | import { mock, instance } from "ts-mockito" 3 | import RootCommandNode from "../../src/lib/tree/RootCommandNode" 4 | import CommandContext from "../../src/lib/context/CommandContext" 5 | import StringReader from "../../src/lib/StringReader" 6 | import CommandContextBuilder from "../../src/lib/context/CommandContextBuilder" 7 | import SuggestionsBuilder from "../../src/lib/suggestion/SuggestionsBuilder" 8 | import { literal } from "../../src/lib/builder/LiteralArgumentBuilder" 9 | import CommandDispatcher from "../../src/lib/CommandDispatcher" 10 | 11 | 12 | describe('RootCommandNodeTest', () => { 13 | let node: RootCommandNode; 14 | 15 | beforeEach(() => { 16 | node = new RootCommandNode(); 17 | }) 18 | 19 | it('testParse', () => { 20 | const reader = new StringReader("hello world"); 21 | node.parse(reader, new CommandContextBuilder(new CommandDispatcher(), new Object(), new RootCommandNode(), 0)); 22 | assert.equal(reader.getCursor(), 0); 23 | }) 24 | 25 | it('testAddChildNoRoot', done => { 26 | try { 27 | node.addChild(new RootCommandNode()); 28 | } catch (ex) { 29 | expect(ex instanceof Error).to.equal(true); 30 | done(); 31 | return; 32 | } 33 | 34 | assert.fail(); 35 | }) 36 | 37 | it('testUsage', () => { 38 | assert.equal(node.getUsageText(), ""); 39 | }) 40 | 41 | it('testSuggestions', async () => { 42 | const context = instance(mock(CommandContext)); 43 | const result = await node.listSuggestions(context, new SuggestionsBuilder("", 0)); 44 | assert.equal(result.isEmpty(), true); 45 | }) 46 | 47 | it('testCreateBuilder', done => { 48 | try { 49 | node.createBuilder(); 50 | } catch (ex) { 51 | expect(ex instanceof Error).to.equal(true); 52 | done(); 53 | return; 54 | } 55 | 56 | assert.fail(); 57 | }) 58 | 59 | it('testEquals', () => { 60 | 61 | assert.equal(new RootCommandNode().equals(new RootCommandNode()), true) 62 | 63 | const temp1 = new RootCommandNode(); 64 | temp1.addChild(literal("foo").build()) 65 | const temp2 = new RootCommandNode() 66 | temp2.addChild(literal("foo").build()) 67 | 68 | assert.equal(temp1.equals(temp2), true) 69 | }) 70 | }) -------------------------------------------------------------------------------- /test/utils/testEquality.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | 3 | interface Equalable { 4 | equals(o: any): boolean; 5 | } 6 | 7 | export default function testEquality(a: Equalable , b: Equalable) { 8 | if(a.equals(b) === false) 9 | assert.fail() 10 | } --------------------------------------------------------------------------------