diff --git a/docs/red-cli.md b/docs/red-cli.md index ae29e5b..9b74a41 100644 --- a/docs/red-cli.md +++ b/docs/red-cli.md @@ -19,7 +19,7 @@ red-cli [flags] --all Ignore project-id -d, --debug Show debug info and raw response -h, --help help for red-cli - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_auth.md b/docs/red-cli_auth.md index 78235d0..4909ded 100644 --- a/docs/red-cli_auth.md +++ b/docs/red-cli_auth.md @@ -24,7 +24,7 @@ red-cli auth [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_auth_list.md b/docs/red-cli_auth_list.md index 0086a2d..670cdcc 100644 --- a/docs/red-cli_auth_list.md +++ b/docs/red-cli_auth_list.md @@ -24,7 +24,7 @@ red-cli auth list [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_auth_login.md b/docs/red-cli_auth_login.md index 542c9f2..5a083b7 100644 --- a/docs/red-cli_auth_login.md +++ b/docs/red-cli_auth_login.md @@ -24,7 +24,7 @@ red-cli auth login [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_auth_logout.md b/docs/red-cli_auth_logout.md index 7befea4..e3595a3 100644 --- a/docs/red-cli_auth_logout.md +++ b/docs/red-cli_auth_logout.md @@ -24,7 +24,7 @@ red-cli auth logout [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_auth_switch.md b/docs/red-cli_auth_switch.md index 722c2b4..716f470 100644 --- a/docs/red-cli_auth_switch.md +++ b/docs/red-cli_auth_switch.md @@ -24,7 +24,7 @@ red-cli auth switch [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_completion.md b/docs/red-cli_completion.md index 76f2812..70e5f17 100644 --- a/docs/red-cli_completion.md +++ b/docs/red-cli_completion.md @@ -62,7 +62,7 @@ red-cli completion -s [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config.md b/docs/red-cli_config.md index 5098175..931732d 100644 --- a/docs/red-cli_config.md +++ b/docs/red-cli_config.md @@ -20,7 +20,7 @@ get or set local or global config ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_editor.md b/docs/red-cli_config_editor.md index f3283c0..55d8682 100644 --- a/docs/red-cli_config_editor.md +++ b/docs/red-cli_config_editor.md @@ -20,7 +20,7 @@ set or get what editor to use globally in ~/.red/config.json ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_editor_set.md b/docs/red-cli_config_editor_set.md index 543e303..f199a40 100644 --- a/docs/red-cli_config_editor_set.md +++ b/docs/red-cli_config_editor_set.md @@ -24,7 +24,7 @@ red-cli config editor set [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_global.md b/docs/red-cli_config_global.md index 9e8dafa..4f07b28 100644 --- a/docs/red-cli_config_global.md +++ b/docs/red-cli_config_global.md @@ -20,7 +20,7 @@ get or set global config ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_global_editor.md b/docs/red-cli_config_global_editor.md index d66eaaa..7521fb0 100644 --- a/docs/red-cli_config_global_editor.md +++ b/docs/red-cli_config_global_editor.md @@ -20,7 +20,7 @@ set or get what editor to use globally in ~/.red/config.json ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_global_editor_set.md b/docs/red-cli_config_global_editor_set.md index 91e355d..75b8953 100644 --- a/docs/red-cli_config_global_editor_set.md +++ b/docs/red-cli_config_global_editor_set.md @@ -24,7 +24,7 @@ red-cli config global editor set [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_global_pager.md b/docs/red-cli_config_global_pager.md index 3ebbd3b..7b95201 100644 --- a/docs/red-cli_config_global_pager.md +++ b/docs/red-cli_config_global_pager.md @@ -20,7 +20,7 @@ set or get what pager to use globally in ~/.red/config.json ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_global_pager_set.md b/docs/red-cli_config_global_pager_set.md index f94dabe..fa2ef71 100644 --- a/docs/red-cli_config_global_pager_set.md +++ b/docs/red-cli_config_global_pager_set.md @@ -24,7 +24,7 @@ red-cli config global pager set [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_global_project.md b/docs/red-cli_config_global_project.md index 252bac5..e3053ac 100644 --- a/docs/red-cli_config_global_project.md +++ b/docs/red-cli_config_global_project.md @@ -20,7 +20,7 @@ set or get a global project under ~/.red/config.json ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_global_project_set.md b/docs/red-cli_config_global_project_set.md index 4a5d9a3..c36968c 100644 --- a/docs/red-cli_config_global_project_set.md +++ b/docs/red-cli_config_global_project_set.md @@ -24,7 +24,7 @@ red-cli config global project set [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_local.md b/docs/red-cli_config_local.md index 6daa1f7..e0f73dc 100644 --- a/docs/red-cli_config_local.md +++ b/docs/red-cli_config_local.md @@ -20,7 +20,7 @@ get or set local config ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_local_project.md b/docs/red-cli_config_local_project.md index 6e964bf..39c6df8 100644 --- a/docs/red-cli_config_local_project.md +++ b/docs/red-cli_config_local_project.md @@ -20,7 +20,7 @@ set or get a local project under ~/.red/config.json ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_local_project_set.md b/docs/red-cli_config_local_project_set.md index 3d9d1cf..aec5f8b 100644 --- a/docs/red-cli_config_local_project_set.md +++ b/docs/red-cli_config_local_project_set.md @@ -24,7 +24,7 @@ red-cli config local project set [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_pager.md b/docs/red-cli_config_pager.md index 08c1615..7cee0d5 100644 --- a/docs/red-cli_config_pager.md +++ b/docs/red-cli_config_pager.md @@ -20,7 +20,7 @@ set or get what pager to use globally in ~/.red/config.json ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_pager_set.md b/docs/red-cli_config_pager_set.md index 7abe8d6..4f9c014 100644 --- a/docs/red-cli_config_pager_set.md +++ b/docs/red-cli_config_pager_set.md @@ -24,7 +24,7 @@ red-cli config pager set [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_project.md b/docs/red-cli_config_project.md index eb70183..592f228 100644 --- a/docs/red-cli_config_project.md +++ b/docs/red-cli_config_project.md @@ -20,7 +20,7 @@ set or get a global project under ~/.red/config.json ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_config_project_set.md b/docs/red-cli_config_project_set.md index 68522de..5297a86 100644 --- a/docs/red-cli_config_project_set.md +++ b/docs/red-cli_config_project_set.md @@ -24,7 +24,7 @@ red-cli config project set [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_issue.md b/docs/red-cli_issue.md index d275e35..fc35725 100644 --- a/docs/red-cli_issue.md +++ b/docs/red-cli_issue.md @@ -24,7 +24,7 @@ red-cli issue [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_issue_create.md b/docs/red-cli_issue_create.md index 1744164..095179c 100644 --- a/docs/red-cli_issue_create.md +++ b/docs/red-cli_issue_create.md @@ -25,7 +25,7 @@ red-cli issue create [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_issue_edit.md b/docs/red-cli_issue_edit.md index c936fe2..e321795 100644 --- a/docs/red-cli_issue_edit.md +++ b/docs/red-cli_issue_edit.md @@ -26,7 +26,7 @@ red-cli issue edit [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_issue_list.md b/docs/red-cli_issue_list.md index 4dd9a7d..c709624 100644 --- a/docs/red-cli_issue_list.md +++ b/docs/red-cli_issue_list.md @@ -24,7 +24,7 @@ red-cli issue list [flags] -o, --offset int skip this number of objects -p, --page int List 25 objects per page (uses limit and offset) --project Display project column - -q, --query string Query for issues with subject + -q, --query string Fuzzy search for issues by subject (supports typos) -s, --sort string Sort field --status_id int Filter on status ID --target_id int Filter on target version ID @@ -35,7 +35,7 @@ red-cli issue list [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_issue_list_all.md b/docs/red-cli_issue_list_all.md index 7a6befb..016ded9 100644 --- a/docs/red-cli_issue_list_all.md +++ b/docs/red-cli_issue_list_all.md @@ -31,8 +31,8 @@ red-cli issue list all [flags] -o, --offset int skip this number of objects -p, --page int List 25 objects per page (uses limit and offset) --project Display project column - -q, --query string Query for issues with subject - --rid string Redmine instance ID (for multi-instance support) + -q, --query string Fuzzy search for issues by subject (supports typos) + --rid string Redmine server name or ID -s, --sort string Sort field --status_id int Filter on status ID --target_id int Filter on target version ID diff --git a/docs/red-cli_issue_list_me.md b/docs/red-cli_issue_list_me.md index b4291de..0d001ab 100644 --- a/docs/red-cli_issue_list_me.md +++ b/docs/red-cli_issue_list_me.md @@ -31,8 +31,8 @@ red-cli issue list me [flags] -o, --offset int skip this number of objects -p, --page int List 25 objects per page (uses limit and offset) --project Display project column - -q, --query string Query for issues with subject - --rid string Redmine instance ID (for multi-instance support) + -q, --query string Fuzzy search for issues by subject (supports typos) + --rid string Redmine server name or ID -s, --sort string Sort field --status_id int Filter on status ID --target_id int Filter on target version ID diff --git a/docs/red-cli_issue_note.md b/docs/red-cli_issue_note.md index 6eb9a5b..40d3d02 100644 --- a/docs/red-cli_issue_note.md +++ b/docs/red-cli_issue_note.md @@ -26,7 +26,7 @@ red-cli issue note [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_issue_view.md b/docs/red-cli_issue_view.md index ae3a7ae..f34b6ea 100644 --- a/docs/red-cli_issue_view.md +++ b/docs/red-cli_issue_view.md @@ -25,7 +25,7 @@ red-cli issue view [id] [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_project.md b/docs/red-cli_project.md index 0258afa..2d46795 100644 --- a/docs/red-cli_project.md +++ b/docs/red-cli_project.md @@ -24,7 +24,7 @@ red-cli project [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_project_list.md b/docs/red-cli_project_list.md index 85c59fd..b354a04 100644 --- a/docs/red-cli_project_list.md +++ b/docs/red-cli_project_list.md @@ -22,7 +22,7 @@ red-cli project list [flags] -l, --limit int Limit number of objects per page (default 25) -o, --offset int skip this number of objects -p, --page int List 25 objects per page (uses limit and offset) - -q, --query string Query for projects with name + -q, --query string Fuzzy search for projects by name (supports typos) -s, --sort string Sort field --status_id int Filter on status ID --target_id int Filter on target version ID @@ -33,7 +33,7 @@ red-cli project list [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_project_list_all.md b/docs/red-cli_project_list_all.md index d5a3e05..7ec0266 100644 --- a/docs/red-cli_project_list_all.md +++ b/docs/red-cli_project_list_all.md @@ -29,8 +29,8 @@ red-cli project list all [flags] -l, --limit int Limit number of objects per page (default 25) -o, --offset int skip this number of objects -p, --page int List 25 objects per page (uses limit and offset) - -q, --query string Query for projects with name - --rid string Redmine instance ID (for multi-instance support) + -q, --query string Fuzzy search for projects by name (supports typos) + --rid string Redmine server name or ID -s, --sort string Sort field --status_id int Filter on status ID --target_id int Filter on target version ID diff --git a/docs/red-cli_user.md b/docs/red-cli_user.md index 3080296..59eb69a 100644 --- a/docs/red-cli_user.md +++ b/docs/red-cli_user.md @@ -24,7 +24,7 @@ red-cli user [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/docs/red-cli_user_me.md b/docs/red-cli_user_me.md index 25c9ada..ce5d9b4 100644 --- a/docs/red-cli_user_me.md +++ b/docs/red-cli_user_me.md @@ -24,7 +24,7 @@ red-cli user me [flags] ``` --all Ignore project-id -d, --debug Show debug info and raw response - --rid string Redmine instance ID (for multi-instance support) + --rid string Redmine server name or ID ``` ### SEE ALSO diff --git a/go.mod b/go.mod index 7037c17..665d2ec 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 github.com/briandowns/spinner v1.23.2 github.com/jedib0t/go-pretty v4.3.0+incompatible + github.com/sahilm/fuzzy v0.1.1 github.com/spf13/cobra v1.10.2 golang.org/x/term v0.39.0 ) @@ -25,6 +26,7 @@ require ( github.com/google/uuid v1.4.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect diff --git a/go.sum b/go.sum index 3eef104..d282b86 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -68,6 +70,8 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= diff --git a/internal/cmd/issue/list.go b/internal/cmd/issue/list.go index f253255..b8d7d48 100644 --- a/internal/cmd/issue/list.go +++ b/internal/cmd/issue/list.go @@ -9,6 +9,7 @@ import ( "github.com/MrJeffLarry/redmine-cli/internal/editor" "github.com/MrJeffLarry/redmine-cli/internal/print" "github.com/MrJeffLarry/redmine-cli/internal/util" + "github.com/sahilm/fuzzy" "github.com/spf13/cobra" ) @@ -47,9 +48,8 @@ func displayListGET(r *config.Red_t, cmd *cobra.Command, path string) { sort = append(sort, "project") } - if query, _ := cmd.Flags().GetString(FLAG_QUERY); query != "" { - path += "subject=~" + query + "&" - } + // Get query string for potential fuzzy matching + query, _ := cmd.Flags().GetString(FLAG_QUERY) path += util.ParseFlags(cmd, projectID, sort) @@ -73,6 +73,25 @@ func displayListGET(r *config.Red_t, cmd *cobra.Command, path string) { return } + // Apply fuzzy search if query is provided + if query != "" { + var subjects []string + for _, issue := range issues.Issues { + subjects = append(subjects, issue.Subject) + } + + matches := fuzzy.Find(query, subjects) + + // Create a new filtered issues list + var filteredIssues []issue + for _, match := range matches { + filteredIssues = append(filteredIssues, issues.Issues[match.Index]) + } + + issues.Issues = filteredIssues + issues.TotalCount = len(filteredIssues) + } + if issue_urls, _ := cmd.Flags().GetBool(FLAG_ISSUE_URLS); issue_urls { for _, issue := range issues.Issues { print.Info(r.Server.Server+"/issues/%d\n", issue.ID) @@ -148,7 +167,7 @@ func cmdIssueList(r *config.Red_t) *cobra.Command { cmd.PersistentFlags().Bool(FLAG_DISPLAY_PROJECT, false, "Display project column") cmd.PersistentFlags().Bool(FLAG_ISSUE_URLS, false, "Show issue urls only") - cmd.PersistentFlags().StringP(FLAG_QUERY, FLAG_QUERY_SHORT, "", "Query for issues with subject") + cmd.PersistentFlags().StringP(FLAG_QUERY, FLAG_QUERY_SHORT, "", "Fuzzy search for issues by subject (supports typos)") util.AddFlags(cmd) diff --git a/internal/cmd/issue/list_test.go b/internal/cmd/issue/list_test.go new file mode 100644 index 0000000..68b0fe4 --- /dev/null +++ b/internal/cmd/issue/list_test.go @@ -0,0 +1,74 @@ +package issue + +import ( + "testing" + + "github.com/sahilm/fuzzy" +) + +func TestFuzzySearch(t *testing.T) { + // Test basic fuzzy matching + subjects := []string{ + "Fix bug in login module", + "Add new feature for user registration", + "Update documentation for API", + "Refactor authentication logic", + "Bug fix for password reset", + } + + // Test exact match + matches := fuzzy.Find("login", subjects) + if len(matches) == 0 { + t.Error("Expected at least one match for 'login'") + } + if matches[0].Str != "Fix bug in login module" { + t.Errorf("Expected first match to be 'Fix bug in login module', got '%s'", matches[0].Str) + } + + // Test fuzzy match with typo + matches = fuzzy.Find("lgn", subjects) + if len(matches) == 0 { + t.Error("Expected at least one match for 'lgn' (typo for login)") + } + + // Test partial match + matches = fuzzy.Find("bug", subjects) + if len(matches) < 2 { + t.Error("Expected at least two matches for 'bug'") + } + + // Test fuzzy match with multiple words + matches = fuzzy.Find("fxbg", subjects) + if len(matches) == 0 { + t.Error("Expected at least one match for 'fxbg' (fuzzy for 'fix bug')") + } +} + +func TestFuzzySearchCaseInsensitive(t *testing.T) { + subjects := []string{ + "Fix Bug in Login Module", + "Add New Feature", + } + + // Test case insensitive matching + matchesLower := fuzzy.Find("fix", subjects) + matchesUpper := fuzzy.Find("FIX", subjects) + matchesMixed := fuzzy.Find("FiX", subjects) + + if len(matchesLower) == 0 || len(matchesUpper) == 0 || len(matchesMixed) == 0 { + t.Error("Fuzzy search should be case insensitive") + } +} + +func TestFuzzySearchNoMatch(t *testing.T) { + subjects := []string{ + "Fix bug in login module", + "Add new feature", + } + + // Test no match scenario + matches := fuzzy.Find("xyz123", subjects) + if len(matches) != 0 { + t.Error("Expected no matches for completely unrelated query") + } +} diff --git a/internal/cmd/project/list.go b/internal/cmd/project/list.go index 6b0d213..2f43e9c 100644 --- a/internal/cmd/project/list.go +++ b/internal/cmd/project/list.go @@ -9,6 +9,7 @@ import ( "github.com/MrJeffLarry/redmine-cli/internal/editor" "github.com/MrJeffLarry/redmine-cli/internal/print" "github.com/MrJeffLarry/redmine-cli/internal/util" + "github.com/sahilm/fuzzy" "github.com/spf13/cobra" ) @@ -27,9 +28,8 @@ func displayListGET(r *config.Red_t, cmd *cobra.Command, path string) { path += util.ParseFlags(cmd, 0, []string{"id", "name"}) - if query, _ := cmd.Flags().GetString(FLAG_QUERY); query != "" { - path += "name=~" + query + "&" - } + // Get query string for potential fuzzy matching + query, _ := cmd.Flags().GetString(FLAG_QUERY) print.Debug(r, path) @@ -51,6 +51,25 @@ func displayListGET(r *config.Red_t, cmd *cobra.Command, path string) { return } + // Apply fuzzy search if query is provided + if query != "" { + var names []string + for _, project := range projects.Projects { + names = append(names, project.Name) + } + + matches := fuzzy.Find(query, names) + + // Create a new filtered projects list + var filteredProjects []project + for _, match := range matches { + filteredProjects = append(filteredProjects, projects.Projects[match.Index]) + } + + projects.Projects = filteredProjects + projects.TotalCount = len(filteredProjects) + } + l := print.NewList(head...) for _, project := range projects.Projects { @@ -93,7 +112,7 @@ func cmdProjectList(r *config.Red_t) *cobra.Command { }, }) - cmd.PersistentFlags().StringP(FLAG_QUERY, FLAG_QUERY_SHORT, "", "Query for projects with name") + cmd.PersistentFlags().StringP(FLAG_QUERY, FLAG_QUERY_SHORT, "", "Fuzzy search for projects by name (supports typos)") util.AddFlags(cmd) diff --git a/internal/cmd/project/list_test.go b/internal/cmd/project/list_test.go new file mode 100644 index 0000000..d7130cb --- /dev/null +++ b/internal/cmd/project/list_test.go @@ -0,0 +1,74 @@ +package project + +import ( + "testing" + + "github.com/sahilm/fuzzy" +) + +func TestFuzzySearchProjects(t *testing.T) { + // Test basic fuzzy matching for project names + names := []string{ + "Website Redesign", + "Mobile Application Development", + "Backend API Refactoring", + "Customer Portal", + "Internal Tools", + } + + // Test exact match + matches := fuzzy.Find("website", names) + if len(matches) == 0 { + t.Error("Expected at least one match for 'website'") + } + if matches[0].Str != "Website Redesign" { + t.Errorf("Expected first match to be 'Website Redesign', got '%s'", matches[0].Str) + } + + // Test fuzzy match with typo + matches = fuzzy.Find("mobil", names) + if len(matches) == 0 { + t.Error("Expected at least one match for 'mobil' (partial match for mobile)") + } + + // Test partial match + matches = fuzzy.Find("api", names) + if len(matches) == 0 { + t.Error("Expected at least one match for 'api'") + } + + // Test fuzzy match with initials + matches = fuzzy.Find("mad", names) + if len(matches) == 0 { + t.Error("Expected at least one match for 'mad' (initials for Mobile Application Development)") + } +} + +func TestFuzzySearchProjectsCaseInsensitive(t *testing.T) { + names := []string{ + "Website Redesign", + "Mobile App", + } + + // Test case insensitive matching + matchesLower := fuzzy.Find("website", names) + matchesUpper := fuzzy.Find("WEBSITE", names) + matchesMixed := fuzzy.Find("WebSite", names) + + if len(matchesLower) == 0 || len(matchesUpper) == 0 || len(matchesMixed) == 0 { + t.Error("Fuzzy search should be case insensitive") + } +} + +func TestFuzzySearchProjectsNoMatch(t *testing.T) { + names := []string{ + "Website Redesign", + "Mobile App", + } + + // Test no match scenario + matches := fuzzy.Find("xyz999", names) + if len(matches) != 0 { + t.Error("Expected no matches for completely unrelated query") + } +}