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

Remove hyphen from command name #43

Merged
merged 6 commits into from Feb 20, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion main.go
Expand Up @@ -42,7 +42,7 @@ func runCmd(config subcommand.Config) error {
return fmt.Errorf(printHelp(config.Commands.Help()))
}
name := strings.Join(os.Args[1:], " ")
d, args, dymMsg, err := config.Commands.Find(name, false)
d, args, dymMsg, err := config.Commands.Find(name)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
printHelp(config.Commands.Help())
Expand Down
2 changes: 1 addition & 1 deletion server/slack/bot-server.go
Expand Up @@ -133,7 +133,7 @@ func findAndExec(config subcommand.Config, text string) (string, error) {
if len(name) == 0 {
return config.Commands.Help(), nil
}
d, args, dymMsg, err := config.Commands.Find(name, true)
d, args, dymMsg, err := config.Commands.Find(name)
if err != nil {
return "", err
}
Expand Down
4 changes: 2 additions & 2 deletions subcommand/air-conditioner-off.go
Expand Up @@ -6,8 +6,8 @@ import (
switchbotsdk "github.com/nasa9084/go-switchbot/v3"
)

const AirConditionerOffCmd = "air-conditioner-off"
const ACOffCmd = "ac-off"
const AirConditionerOffCmd = "air conditioner off"
const ACOffCmd = "ac off"

func NewAirConditionerOffDefinition() Definition {
return Definition{
Expand Down
4 changes: 2 additions & 2 deletions subcommand/air-conditioner-on.go
Expand Up @@ -6,8 +6,8 @@ import (
switchbotsdk "github.com/nasa9084/go-switchbot/v3"
)

const AirConditionerOnCmd = "air-conditioner-on"
const ACOnCmd = "ac-on"
const AirConditionerOnCmd = "air conditioner on"
const ACOnCmd = "ac on"

func NewAirConditionerOnDefinition() Definition {
return Definition{
Expand Down
2 changes: 1 addition & 1 deletion subcommand/change-playlist.go
Expand Up @@ -5,7 +5,7 @@ import (
"github.com/johtani/smarthome/subcommand/action/owntone"
)

const ChangePlaylistCmd = "change-playlist"
const ChangePlaylistCmd = "change playlist"

func NewChangePlaylistCmdDefinition() Definition {
return Definition{
Expand Down
4 changes: 2 additions & 2 deletions subcommand/display-temperature.go
Expand Up @@ -5,8 +5,8 @@ import (
"github.com/johtani/smarthome/subcommand/action/switchbot"
)

const DisplayTemperatureCmd = "display-temperature"
const DispTempCmd = "disp-temp"
const DisplayTemperatureCmd = "display temperature"
const DispTempCmd = "disp temp"

func NewDisplayTemperatureDefinition() Definition {
return Definition{
Expand Down
2 changes: 1 addition & 1 deletion subcommand/finish-meeting.go
Expand Up @@ -6,7 +6,7 @@ import (
"time"
)

const FinishMeetingCmd = "finish-meeting"
const FinishMeetingCmd = "finish meeting"

func NewFinishMeetingDefinition() Definition {
return Definition{
Expand Down
2 changes: 1 addition & 1 deletion subcommand/light-off.go
Expand Up @@ -6,7 +6,7 @@ import (
switchbotsdk "github.com/nasa9084/go-switchbot/v3"
)

const LightOffCmd = "light-off"
const LightOffCmd = "light off"

func NewLightOffDefinition() Definition {
return Definition{
Expand Down
2 changes: 1 addition & 1 deletion subcommand/light-on.go
Expand Up @@ -6,7 +6,7 @@ import (
switchbotsdk "github.com/nasa9084/go-switchbot/v3"
)

const LightOnCmd = "light-on"
const LightOnCmd = "light on"

func NewLightOnDefinition() Definition {
return Definition{
Expand Down
2 changes: 1 addition & 1 deletion subcommand/start-meeting.go
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/johtani/smarthome/subcommand/action/yamaha"
)

const StartMeetingCmd = "start-meeting"
const StartMeetingCmd = "start meeting"

func NewStartMeetingDefinition() Definition {
return Definition{
Expand Down
2 changes: 1 addition & 1 deletion subcommand/start-music.go
@@ -1,6 +1,6 @@
package subcommand

const StartMusicCmd = "start-music"
const StartMusicCmd = "start music"

func NewStartMusicCmdDefinition() Definition {
return Definition{
Expand Down
2 changes: 1 addition & 1 deletion subcommand/start-ps5.go
Expand Up @@ -6,7 +6,7 @@ import (
"time"
)

const StartPS5Cmd = "start-ps5"
const StartPS5Cmd = "start ps5"

func NewStartPS5Definition() Definition {
return Definition{
Expand Down
2 changes: 1 addition & 1 deletion subcommand/start-switch.go
Expand Up @@ -6,7 +6,7 @@ import (
"time"
)

const StartSwitchCmd = "start-switch"
const StartSwitchCmd = "start switch"

func NewStartSwitchDefinition() Definition {
return Definition{
Expand Down
2 changes: 1 addition & 1 deletion subcommand/stop-music.go
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/johtani/smarthome/subcommand/action/yamaha"
)

const StopMusicCmd = "stop-music"
const StopMusicCmd = "stop music"

func NewStopMusicDefinition() Definition {
return Definition{
Expand Down
112 changes: 40 additions & 72 deletions subcommand/subcommand.go
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/hbollon/go-edlib"
"github.com/johtani/smarthome/subcommand/action"
"strings"
"unicode"
)

type Subcommand struct {
Expand Down Expand Up @@ -50,80 +51,48 @@ func (d Definition) Help() string {
return help
}

func (d Definition) noHyphens() []string {
noHyphens := []string{strings.ReplaceAll(d.Name, "-", " ")}
for _, shortname := range d.shortnames {
noHyphens = append(noHyphens, strings.ReplaceAll(shortname, "-", " "))
}
return noHyphens
}

func (d Definition) Distance(name string, withoutHyphen bool) (int, string) {
distance := edlib.LevenshteinDistance(name, d.Name)
command := d.Name
func (d Definition) Distance(input string) (int, string) {
// 対象にならないサイズを初期値として設定
distance := dymCandidateDistance + 1
command := ""
// TODO shortnameどうする?一番小さいDistanceでいいか?
if len(d.shortnames) > 0 {
for _, tmp := range d.shortnames {
sd := edlib.LevenshteinDistance(name, tmp)
if sd < distance {
distance = sd
command = tmp
}
}
}
if withoutHyphen && len(d.noHyphens()) > 0 {
for _, tmp := range d.noHyphens() {
sd := edlib.LevenshteinDistance(name, tmp)
if sd < distance {
distance = sd
command = tmp
}
for _, cmd := range append([]string{d.Name}, d.shortnames...) {
sd := edlib.LevenshteinDistance(input, cmd)
if sd < distance {
distance = sd
command = cmd
}
}
return distance, command
}

func (d Definition) Match(message string, withoutHyphen bool) (bool, string, error) {
var match bool = false
var args string = ""
if d.WithArgs {
params := strings.SplitN(message, " ", 2)
if len(params) < 2 {
return match, args, fmt.Errorf("%s is not supported without arguments", message)
}
if d.IsTarget(params[0], withoutHyphen) {
match = true
args = params[1]
}
} else {
if d.IsTarget(message, withoutHyphen) {
match = true
args = ""
func (d Definition) Match(message string) (bool, string) {
var args = ""
inputs := strings.Fields(message)
for _, cmd := range append([]string{d.Name}, d.shortnames...) {
cmds := strings.Fields(cmd)
if d.isPrefix(inputs, cmds) {
args = message
for _, cmd := range cmds {
args = strings.Replace(args, cmd, "", 1)
args = strings.TrimLeftFunc(args, unicode.IsSpace)
}
return true, args
}
}
return match, args, nil
}

func DefaultMatch(message string) (bool, string) {
return false, ""
return false, args
}

func (d Definition) IsTarget(name string, withoutHyphen bool) bool {
if withoutHyphen {
return name == d.Name || d.contains(d.shortnames, name) || d.contains(d.noHyphens(), name)
} else {
return name == d.Name || d.contains(d.shortnames, name)
func (d Definition) isPrefix(inputs []string, cmd []string) bool {
if len(inputs) == 0 || len(cmd) == 0 || len(inputs) < len(cmd) {
return false
}
}

// slices.Contains support >= Go 1.21
func (d Definition) contains(names []string, target string) bool {
for _, name := range names {
if target == name {
return true
for i := 0; i < len(cmd); i++ {
if cmd[i] != inputs[i] {
return false
}
}
return false
return true
}

type Commands struct {
Expand Down Expand Up @@ -157,26 +126,23 @@ func NewCommands() Commands {
}
}

func (c Commands) Find(name string, withoutHyphen bool) (Definition, string, string, error) {
func (c Commands) Find(text string) (Definition, string, string, error) {
var def Definition
var args string
dymMsg := ""
find := false
for _, d := range c.definitions {
var err error
find, args, err = d.Match(name, withoutHyphen)
if err != nil {
return Definition{}, "", "", err
} else if find {
find, args = d.Match(text)
if find {
def = d
break
}
}

if find == false {
candidates, cmds := c.didYouMean(name, true)
candidates, cmds := c.didYouMean(text)
if len(candidates) == 0 {
return Definition{}, "", "", fmt.Errorf("Sorry, I cannot understand what you want from what you said '%v'...\n", name)
return Definition{}, "", "", fmt.Errorf("Sorry, I cannot understand what you want from what you said '%v'...\n", text)
} else {
def = candidates[0]
dymMsg = fmt.Sprintf("Did you mean \"%v\"?", cmds[0])
Expand All @@ -186,13 +152,15 @@ func (c Commands) Find(name string, withoutHyphen bool) (Definition, string, str
return def, args, dymMsg, nil
}

func (c Commands) didYouMean(name string, withoutHyphen bool) ([]Definition, []string) {
const dymCandidateDistance = 3

func (c Commands) didYouMean(name string) ([]Definition, []string) {
var candidates []Definition
var cmds []string
for _, def := range c.definitions {
d, cmd := def.Distance(name, withoutHyphen)
d, cmd := def.Distance(name)
// TODO 3にした場合は、candidatesの距離の小さい順で返したほうが便利な気がする
if d < 3 {
if d < dymCandidateDistance {
candidates = append(candidates, def)
cmds = append(cmds, cmd)
}
Expand Down