Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small iteration on command parser #4998

Merged
merged 6 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/4998.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Small iteration on command parser and unit test it.
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
import org.matrix.android.sdk.api.session.identity.ThreePid
import timber.log.Timber
import javax.inject.Inject

object CommandParser {
class CommandParser @Inject constructor() {

/**
* Convert the text message into a Slash command.
Expand All @@ -34,11 +35,9 @@ object CommandParser {
*/
fun parseSlashCommand(textMessage: CharSequence): ParsedCommand {
// check if it has the Slash marker
if (!textMessage.startsWith("/")) {
return ParsedCommand.ErrorNotACommand
return if (!textMessage.startsWith("/")) {
ParsedCommand.ErrorNotACommand
} else {
Timber.v("parseSlashCommand")

// "/" only
if (textMessage.length == 1) {
return ParsedCommand.ErrorEmptySlashCommand
Expand All @@ -52,7 +51,7 @@ object CommandParser {
val messageParts = try {
textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
} catch (e: Exception) {
Timber.e(e, "## manageSlashCommand() : split failed")
Timber.e(e, "## parseSlashCommand() : split failed")
null
}

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

return when {
when {
Command.PLAIN.matches(slashCommand) -> {
if (message.isNotEmpty()) {
ParsedCommand.SendPlainText(message = message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,51 +22,51 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
/**
* Represent a parsed command
*/
sealed class ParsedCommand {
sealed interface ParsedCommand {
// This is not a Slash command
object ErrorNotACommand : ParsedCommand()
object ErrorNotACommand : ParsedCommand

object ErrorEmptySlashCommand : ParsedCommand()
object ErrorEmptySlashCommand : ParsedCommand

// Unknown/Unsupported slash command
class ErrorUnknownSlashCommand(val slashCommand: String) : ParsedCommand()
data class ErrorUnknownSlashCommand(val slashCommand: String) : ParsedCommand

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

// Valid commands:

class SendPlainText(val message: CharSequence) : ParsedCommand()
class SendEmote(val message: CharSequence) : ParsedCommand()
class SendRainbow(val message: CharSequence) : ParsedCommand()
class SendRainbowEmote(val message: CharSequence) : ParsedCommand()
class BanUser(val userId: String, val reason: String?) : ParsedCommand()
class UnbanUser(val userId: String, val reason: String?) : ParsedCommand()
class IgnoreUser(val userId: String) : ParsedCommand()
class UnignoreUser(val userId: String) : ParsedCommand()
class SetUserPowerLevel(val userId: String, val powerLevel: Int?) : ParsedCommand()
class ChangeRoomName(val name: String) : ParsedCommand()
class Invite(val userId: String, val reason: String?) : ParsedCommand()
class Invite3Pid(val threePid: ThreePid) : ParsedCommand()
class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
class PartRoom(val roomAlias: String?) : ParsedCommand()
class ChangeTopic(val topic: String) : ParsedCommand()
class RemoveUser(val userId: String, val reason: String?) : ParsedCommand()
class ChangeDisplayName(val displayName: String) : ParsedCommand()
class ChangeDisplayNameForRoom(val displayName: String) : ParsedCommand()
class ChangeRoomAvatar(val url: String) : ParsedCommand()
class ChangeAvatarForRoom(val url: String) : ParsedCommand()
class SetMarkdown(val enable: Boolean) : ParsedCommand()
object ClearScalarToken : ParsedCommand()
class SendSpoiler(val message: String) : ParsedCommand()
class SendShrug(val message: CharSequence) : ParsedCommand()
class SendLenny(val message: CharSequence) : ParsedCommand()
object DiscardSession : ParsedCommand()
class ShowUser(val userId: String) : ParsedCommand()
class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand()
class CreateSpace(val name: String, val invitees: List<String>) : ParsedCommand()
class AddToSpace(val spaceId: String) : ParsedCommand()
class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand()
class LeaveRoom(val roomId: String) : ParsedCommand()
class UpgradeRoom(val newVersion: String) : ParsedCommand()
data class SendPlainText(val message: CharSequence) : ParsedCommand
data class SendEmote(val message: CharSequence) : ParsedCommand
data class SendRainbow(val message: CharSequence) : ParsedCommand
data class SendRainbowEmote(val message: CharSequence) : ParsedCommand
data class BanUser(val userId: String, val reason: String?) : ParsedCommand
data class UnbanUser(val userId: String, val reason: String?) : ParsedCommand
data class IgnoreUser(val userId: String) : ParsedCommand
data class UnignoreUser(val userId: String) : ParsedCommand
data class SetUserPowerLevel(val userId: String, val powerLevel: Int?) : ParsedCommand
data class ChangeRoomName(val name: String) : ParsedCommand
data class Invite(val userId: String, val reason: String?) : ParsedCommand
data class Invite3Pid(val threePid: ThreePid) : ParsedCommand
data class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand
data class PartRoom(val roomAlias: String?) : ParsedCommand
data class ChangeTopic(val topic: String) : ParsedCommand
data class RemoveUser(val userId: String, val reason: String?) : ParsedCommand
data class ChangeDisplayName(val displayName: String) : ParsedCommand
data class ChangeDisplayNameForRoom(val displayName: String) : ParsedCommand
data class ChangeRoomAvatar(val url: String) : ParsedCommand
data class ChangeAvatarForRoom(val url: String) : ParsedCommand
data class SetMarkdown(val enable: Boolean) : ParsedCommand
object ClearScalarToken : ParsedCommand
data class SendSpoiler(val message: String) : ParsedCommand
data class SendShrug(val message: CharSequence) : ParsedCommand
data class SendLenny(val message: CharSequence) : ParsedCommand
object DiscardSession : ParsedCommand
data class ShowUser(val userId: String) : ParsedCommand
data class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand
data class CreateSpace(val name: String, val invitees: List<String>) : ParsedCommand
data class AddToSpace(val spaceId: String) : ParsedCommand
data class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand
data class LeaveRoom(val roomId: String) : ParsedCommand
data class UpgradeRoom(val newVersion: String) : ParsedCommand
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class MessageComposerViewModel @AssistedInject constructor(
private val session: Session,
private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences,
private val commandParser: CommandParser,
private val rainbowGenerator: RainbowGenerator,
private val voiceMessageHelper: VoiceMessageHelper,
private val voicePlayerHelper: VoicePlayerHelper
Expand Down Expand Up @@ -183,7 +184,7 @@ class MessageComposerViewModel @AssistedInject constructor(
withState { state ->
when (state.sendMode) {
is SendMode.Regular -> {
when (val slashCommandResult = CommandParser.parseSlashCommand(action.text)) {
when (val slashCommandResult = commandParser.parseSlashCommand(action.text)) {
is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.command

import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test

class CommandParserTest {
@Test
fun parseSlashCommandEmpty() {
test("/", ParsedCommand.ErrorEmptySlashCommand)
}

@Test
fun parseSlashCommandUnknown() {
test("/unknown", ParsedCommand.ErrorUnknownSlashCommand("/unknown"))
test("/unknown with param", ParsedCommand.ErrorUnknownSlashCommand("/unknown"))
}

@Test
fun parseSlashCommandNotACommand() {
test("", ParsedCommand.ErrorNotACommand)
test("test", ParsedCommand.ErrorNotACommand)
test("// test", ParsedCommand.ErrorNotACommand)
}

@Test
fun parseSlashCommandEmote() {
test("/me test", ParsedCommand.SendEmote("test"))
test("/me", ParsedCommand.ErrorSyntax(Command.EMOTE))
}

@Test
fun parseSlashCommandRemove() {
// Nominal
test("/remove @foo:bar", ParsedCommand.RemoveUser("@foo:bar", null))
// With a reason
test("/remove @foo:bar a reason", ParsedCommand.RemoveUser("@foo:bar", "a reason"))
// Trim the reason
test("/remove @foo:bar a reason ", ParsedCommand.RemoveUser("@foo:bar", "a reason"))
// Alias
test("/kick @foo:bar", ParsedCommand.RemoveUser("@foo:bar", null))
// Error
test("/remove", ParsedCommand.ErrorSyntax(Command.REMOVE_USER))
}

private fun test(message: String, expectedResult: ParsedCommand) {
val commandParser = CommandParser()
val result = commandParser.parseSlashCommand(message)
result shouldBeEqualTo expectedResult
}
}