Skip to content

Commit 01ff85d

Browse files
authored
Merge pull request #4998 from vector-im/feature/bma/command_parser
Small iteration on command parser
2 parents d7b919a + e9f9c7e commit 01ff85d

File tree

5 files changed

+113
-46
lines changed

5 files changed

+113
-46
lines changed

changelog.d/4998.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Small iteration on command parser and unit test it.

vector/src/main/java/im/vector/app/features/command/CommandParser.kt

+6-7
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ import org.matrix.android.sdk.api.MatrixPatterns
2323
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
2424
import org.matrix.android.sdk.api.session.identity.ThreePid
2525
import timber.log.Timber
26+
import javax.inject.Inject
2627

27-
object CommandParser {
28+
class CommandParser @Inject constructor() {
2829

2930
/**
3031
* Convert the text message into a Slash command.
@@ -34,11 +35,9 @@ object CommandParser {
3435
*/
3536
fun parseSlashCommand(textMessage: CharSequence): ParsedCommand {
3637
// check if it has the Slash marker
37-
if (!textMessage.startsWith("/")) {
38-
return ParsedCommand.ErrorNotACommand
38+
return if (!textMessage.startsWith("/")) {
39+
ParsedCommand.ErrorNotACommand
3940
} else {
40-
Timber.v("parseSlashCommand")
41-
4241
// "/" only
4342
if (textMessage.length == 1) {
4443
return ParsedCommand.ErrorEmptySlashCommand
@@ -52,7 +51,7 @@ object CommandParser {
5251
val messageParts = try {
5352
textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
5453
} catch (e: Exception) {
55-
Timber.e(e, "## manageSlashCommand() : split failed")
54+
Timber.e(e, "## parseSlashCommand() : split failed")
5655
null
5756
}
5857

@@ -64,7 +63,7 @@ object CommandParser {
6463
val slashCommand = messageParts.first()
6564
val message = textMessage.substring(slashCommand.length).trim()
6665

67-
return when {
66+
when {
6867
Command.PLAIN.matches(slashCommand) -> {
6968
if (message.isNotEmpty()) {
7069
ParsedCommand.SendPlainText(message = message)

vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt

+38-38
Original file line numberDiff line numberDiff line change
@@ -22,51 +22,51 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
2222
/**
2323
* Represent a parsed command
2424
*/
25-
sealed class ParsedCommand {
25+
sealed interface ParsedCommand {
2626
// This is not a Slash command
27-
object ErrorNotACommand : ParsedCommand()
27+
object ErrorNotACommand : ParsedCommand
2828

29-
object ErrorEmptySlashCommand : ParsedCommand()
29+
object ErrorEmptySlashCommand : ParsedCommand
3030

3131
// Unknown/Unsupported slash command
32-
class ErrorUnknownSlashCommand(val slashCommand: String) : ParsedCommand()
32+
data class ErrorUnknownSlashCommand(val slashCommand: String) : ParsedCommand
3333

3434
// A slash command is detected, but there is an error
35-
class ErrorSyntax(val command: Command) : ParsedCommand()
35+
data class ErrorSyntax(val command: Command) : ParsedCommand
3636

3737
// Valid commands:
3838

39-
class SendPlainText(val message: CharSequence) : ParsedCommand()
40-
class SendEmote(val message: CharSequence) : ParsedCommand()
41-
class SendRainbow(val message: CharSequence) : ParsedCommand()
42-
class SendRainbowEmote(val message: CharSequence) : ParsedCommand()
43-
class BanUser(val userId: String, val reason: String?) : ParsedCommand()
44-
class UnbanUser(val userId: String, val reason: String?) : ParsedCommand()
45-
class IgnoreUser(val userId: String) : ParsedCommand()
46-
class UnignoreUser(val userId: String) : ParsedCommand()
47-
class SetUserPowerLevel(val userId: String, val powerLevel: Int?) : ParsedCommand()
48-
class ChangeRoomName(val name: String) : ParsedCommand()
49-
class Invite(val userId: String, val reason: String?) : ParsedCommand()
50-
class Invite3Pid(val threePid: ThreePid) : ParsedCommand()
51-
class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
52-
class PartRoom(val roomAlias: String?) : ParsedCommand()
53-
class ChangeTopic(val topic: String) : ParsedCommand()
54-
class RemoveUser(val userId: String, val reason: String?) : ParsedCommand()
55-
class ChangeDisplayName(val displayName: String) : ParsedCommand()
56-
class ChangeDisplayNameForRoom(val displayName: String) : ParsedCommand()
57-
class ChangeRoomAvatar(val url: String) : ParsedCommand()
58-
class ChangeAvatarForRoom(val url: String) : ParsedCommand()
59-
class SetMarkdown(val enable: Boolean) : ParsedCommand()
60-
object ClearScalarToken : ParsedCommand()
61-
class SendSpoiler(val message: String) : ParsedCommand()
62-
class SendShrug(val message: CharSequence) : ParsedCommand()
63-
class SendLenny(val message: CharSequence) : ParsedCommand()
64-
object DiscardSession : ParsedCommand()
65-
class ShowUser(val userId: String) : ParsedCommand()
66-
class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand()
67-
class CreateSpace(val name: String, val invitees: List<String>) : ParsedCommand()
68-
class AddToSpace(val spaceId: String) : ParsedCommand()
69-
class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand()
70-
class LeaveRoom(val roomId: String) : ParsedCommand()
71-
class UpgradeRoom(val newVersion: String) : ParsedCommand()
39+
data class SendPlainText(val message: CharSequence) : ParsedCommand
40+
data class SendEmote(val message: CharSequence) : ParsedCommand
41+
data class SendRainbow(val message: CharSequence) : ParsedCommand
42+
data class SendRainbowEmote(val message: CharSequence) : ParsedCommand
43+
data class BanUser(val userId: String, val reason: String?) : ParsedCommand
44+
data class UnbanUser(val userId: String, val reason: String?) : ParsedCommand
45+
data class IgnoreUser(val userId: String) : ParsedCommand
46+
data class UnignoreUser(val userId: String) : ParsedCommand
47+
data class SetUserPowerLevel(val userId: String, val powerLevel: Int?) : ParsedCommand
48+
data class ChangeRoomName(val name: String) : ParsedCommand
49+
data class Invite(val userId: String, val reason: String?) : ParsedCommand
50+
data class Invite3Pid(val threePid: ThreePid) : ParsedCommand
51+
data class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand
52+
data class PartRoom(val roomAlias: String?) : ParsedCommand
53+
data class ChangeTopic(val topic: String) : ParsedCommand
54+
data class RemoveUser(val userId: String, val reason: String?) : ParsedCommand
55+
data class ChangeDisplayName(val displayName: String) : ParsedCommand
56+
data class ChangeDisplayNameForRoom(val displayName: String) : ParsedCommand
57+
data class ChangeRoomAvatar(val url: String) : ParsedCommand
58+
data class ChangeAvatarForRoom(val url: String) : ParsedCommand
59+
data class SetMarkdown(val enable: Boolean) : ParsedCommand
60+
object ClearScalarToken : ParsedCommand
61+
data class SendSpoiler(val message: String) : ParsedCommand
62+
data class SendShrug(val message: CharSequence) : ParsedCommand
63+
data class SendLenny(val message: CharSequence) : ParsedCommand
64+
object DiscardSession : ParsedCommand
65+
data class ShowUser(val userId: String) : ParsedCommand
66+
data class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand
67+
data class CreateSpace(val name: String, val invitees: List<String>) : ParsedCommand
68+
data class AddToSpace(val spaceId: String) : ParsedCommand
69+
data class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand
70+
data class LeaveRoom(val roomId: String) : ParsedCommand
71+
data class UpgradeRoom(val newVersion: String) : ParsedCommand
7272
}

vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class MessageComposerViewModel @AssistedInject constructor(
6666
private val session: Session,
6767
private val stringProvider: StringProvider,
6868
private val vectorPreferences: VectorPreferences,
69+
private val commandParser: CommandParser,
6970
private val rainbowGenerator: RainbowGenerator,
7071
private val voiceMessageHelper: VoiceMessageHelper,
7172
private val voicePlayerHelper: VoicePlayerHelper
@@ -183,7 +184,7 @@ class MessageComposerViewModel @AssistedInject constructor(
183184
withState { state ->
184185
when (state.sendMode) {
185186
is SendMode.Regular -> {
186-
when (val slashCommandResult = CommandParser.parseSlashCommand(action.text)) {
187+
when (val slashCommandResult = commandParser.parseSlashCommand(action.text)) {
187188
is ParsedCommand.ErrorNotACommand -> {
188189
// Send the text message to the room
189190
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.command
18+
19+
import org.amshove.kluent.shouldBeEqualTo
20+
import org.junit.Test
21+
22+
class CommandParserTest {
23+
@Test
24+
fun parseSlashCommandEmpty() {
25+
test("/", ParsedCommand.ErrorEmptySlashCommand)
26+
}
27+
28+
@Test
29+
fun parseSlashCommandUnknown() {
30+
test("/unknown", ParsedCommand.ErrorUnknownSlashCommand("/unknown"))
31+
test("/unknown with param", ParsedCommand.ErrorUnknownSlashCommand("/unknown"))
32+
}
33+
34+
@Test
35+
fun parseSlashCommandNotACommand() {
36+
test("", ParsedCommand.ErrorNotACommand)
37+
test("test", ParsedCommand.ErrorNotACommand)
38+
test("// test", ParsedCommand.ErrorNotACommand)
39+
}
40+
41+
@Test
42+
fun parseSlashCommandEmote() {
43+
test("/me test", ParsedCommand.SendEmote("test"))
44+
test("/me", ParsedCommand.ErrorSyntax(Command.EMOTE))
45+
}
46+
47+
@Test
48+
fun parseSlashCommandRemove() {
49+
// Nominal
50+
test("/remove @foo:bar", ParsedCommand.RemoveUser("@foo:bar", null))
51+
// With a reason
52+
test("/remove @foo:bar a reason", ParsedCommand.RemoveUser("@foo:bar", "a reason"))
53+
// Trim the reason
54+
test("/remove @foo:bar a reason ", ParsedCommand.RemoveUser("@foo:bar", "a reason"))
55+
// Alias
56+
test("/kick @foo:bar", ParsedCommand.RemoveUser("@foo:bar", null))
57+
// Error
58+
test("/remove", ParsedCommand.ErrorSyntax(Command.REMOVE_USER))
59+
}
60+
61+
private fun test(message: String, expectedResult: ParsedCommand) {
62+
val commandParser = CommandParser()
63+
val result = commandParser.parseSlashCommand(message)
64+
result shouldBeEqualTo expectedResult
65+
}
66+
}

0 commit comments

Comments
 (0)