diff --git a/cmd/add.go b/cmd/add.go index 14a37f8..da48f3b 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -8,12 +8,17 @@ import ( // addCmd represents the add command var addCmd = &cobra.Command{ - Use: "add", + Use: "add [FUZZY_COUNTRY_NAME?]", Short: "Add one or more clock", PostRunE: saveConfig, + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { oldNumClocks := len(cfg.ClockCfgs) - clockCfgs := ui.SelectClocks() + searchTerm := "" + if len(args) >= 1 { + searchTerm = args[0] + } + clockCfgs := ui.SelectClocks(searchTerm) cfg.ClockCfgs.Add(clockCfgs...) newNumClocks := len(cfg.ClockCfgs) diff --git a/cmd/root.go b/cmd/root.go index 8676d87..96168d0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,9 +29,10 @@ var ( cfg ui.AppConfig rootCmd = &cobra.Command{ - Use: "clocks", + Use: "clocks [FUZZY_CLOCK_NAME?]", Short: "A tool to display time across multiple timezones.", PersistentPreRunE: loadConfig, + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { if clocksAbsent() { return @@ -52,6 +53,38 @@ var ( if twelveHr { cfg.TwelveHour = true } + + if len(args) >= 1 { + // when a @search term is passed, set layout to horizontal + // so that all clocks are displayed in one row + // clocks are filtered by the search term + // fuzzy search is used to match the search term + searchTerm := args[0] + cfg.Layout.LayoutType = ui.Horizontal + + n := 0 + cfg.ClockCfgs, n = cfg.ClockCfgs.Filter(searchTerm) + if n == 0 { + pterm.FgYellow.Println("No clocks match the search term:", searchTerm) + return + } + } + + if len(args) >= 1 { + // when a @search term is passed, set layout to horizontal + // so that all clocks are displayed in one row + // clocks are filtered by the search term + // fuzzy search is used to match the search term + searchTerm := args[0] + cfg.Layout.LayoutType = ui.Horizontal + + n := 0 + cfg.ClockCfgs, n = cfg.ClockCfgs.Filter(searchTerm) + if n == 0 { + pterm.FgYellow.Println("No clocks match the search term:", searchTerm) + return + } + } ui.ShowClocks(cfg) }, @@ -152,7 +185,6 @@ func addFlags(cmd *cobra.Command) { cmd.Flags().BoolVarP(&live, "live", "l", false, "keeps clocks on screen") cmd.Flags().BoolVarP(&seconds, "seconds", "s", false, "shows seconds as well") cmd.Flags().BoolVar(&twelveHr, "t12", false, "print dates in 12 hour format") - } func fatal(err error, fmt string, args ...any) { diff --git a/go.mod b/go.mod index f730636..1f55836 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/prnvbn/clocks -go 1.21.3 +go 1.23.1 require ( github.com/adrg/xdg v0.4.0 + github.com/lithammer/fuzzysearch v1.1.8 github.com/pkg/errors v0.9.1 github.com/pterm/pterm v0.12.75 github.com/rs/zerolog v1.31.0 @@ -21,7 +22,6 @@ require ( github.com/gookit/color v1.5.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/text v0.2.0 // indirect - github.com/lithammer/fuzzysearch v1.1.8 // 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/internal/match/fuzzy.go b/internal/match/fuzzy.go new file mode 100644 index 0000000..821b338 --- /dev/null +++ b/internal/match/fuzzy.go @@ -0,0 +1,11 @@ +package match + +import ( + "strings" + + "github.com/lithammer/fuzzysearch/fuzzy" +) + +func Fuzzy(searchTerm string, toMatch string) bool { + return fuzzy.Match(strings.ToLower(searchTerm), strings.ToLower(toMatch)) +} diff --git a/internal/tmz/filtered.go b/internal/tmz/filtered.go new file mode 100644 index 0000000..ee2d4a8 --- /dev/null +++ b/internal/tmz/filtered.go @@ -0,0 +1,20 @@ +package tmz + +import ( + "iter" + + "github.com/prnvbn/clocks/internal/match" +) + +func FilteredCountries(searchTerm string) iter.Seq[string] { + return func(yield func(string) bool) { + for cntry := range CountryZonesMap { + if match.Fuzzy(searchTerm, cntry) { + if !yield(cntry) { + return + } + } + } + } + +} diff --git a/internal/ui/clocks.go b/internal/ui/clocks.go index a77be10..e69102f 100644 --- a/internal/ui/clocks.go +++ b/internal/ui/clocks.go @@ -42,7 +42,6 @@ func ShowClocks(appCfg AppConfig) { } time.Sleep(time.Second) - } } diff --git a/internal/ui/config.go b/internal/ui/config.go index 22d5389..7d68230 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -3,8 +3,11 @@ package ui import ( "encoding/json" "fmt" + "iter" "slices" + "strings" + "github.com/prnvbn/clocks/internal/match" "github.com/prnvbn/clocks/internal/tmz" ) @@ -93,3 +96,21 @@ func (s *SortedClockConfigs) Remove(toRemove ...ClockConfig) { return slices.Contains(toRemove, cfg) }) } + +func (s SortedClockConfigs) Filter(searchTerm string) (filtered SortedClockConfigs, n int) { + filtered = slices.Collect(s.fuzzyFiltered(searchTerm)) + n = len(filtered) + return +} + +func (s SortedClockConfigs) fuzzyFiltered(searchTerm string) iter.Seq[ClockConfig] { + return func(yield func(ClockConfig) bool) { + for _, clockCfg := range s { + if match.Fuzzy(strings.ToLower(searchTerm), strings.ToLower(clockCfg.Heading)) { + if !yield(clockCfg) { + return + } + } + } + } +} diff --git a/internal/ui/select_clocks.go b/internal/ui/select_clocks.go index b0748f1..205c33d 100644 --- a/internal/ui/select_clocks.go +++ b/internal/ui/select_clocks.go @@ -7,7 +7,6 @@ import ( "github.com/prnvbn/clocks/internal/tmz" "github.com/pterm/pterm" - "golang.org/x/exp/maps" "golang.org/x/term" ) @@ -15,8 +14,9 @@ const ( minMaxHeight = 5 ) -func SelectClocks() []ClockConfig { - cntries := maps.Keys(tmz.CountryZonesMap) +func SelectClocks(searchTerm string) []ClockConfig { + cntries := slices.Collect(tmz.FilteredCountries(searchTerm)) + slices.Sort(cntries) cntryMenuHeading := pterm.ThemeDefault.PrimaryStyle.Sprint("Please select countries; you can select the timezones after this ") + pterm.ThemeDefault.SecondaryStyle.Sprint("[type to search]")