From 26c5b97823587d7385ef5fc2d618b328cec9facc Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Tue, 24 Mar 2026 17:14:29 +0200 Subject: [PATCH] refactor: use samber/lo wherever possible --- appconfig/appconfig.go | 24 ++------ appconfig/appconfig_test.go | 17 +++--- cmd/root.go | 18 +++--- installer/apt_installer_test.go | 13 +++-- installer/brew_installer_test.go | 49 ++++++++-------- installer/cargo_installer_test.go | 21 +++---- installer/docker_installer_test.go | 9 +-- installer/filter.go | 19 +++--- installer/filter_test.go | 67 +++++++++++----------- installer/git_installer_test.go | 17 +++--- installer/github_release_installer_test.go | 59 +++++++++---------- installer/group_installer_test.go | 35 +++++------ installer/installer_test.go | 3 +- installer/manifest_installer_test.go | 31 +++++----- installer/npm_installer_test.go | 13 +++-- installer/pacman_installer_test.go | 23 ++++---- installer/pipx_installer_test.go | 13 +++-- installer/rsync_installer_test.go | 9 +-- installer/shell_installer_test.go | 9 +-- installer/testutils.go | 5 -- machine/machine.go | 18 ++---- platform/platform.go | 13 ++--- platform/platform_test.go | 17 +++--- summary/summary.go | 25 +++----- utils/env.go | 10 ++-- 25 files changed, 252 insertions(+), 285 deletions(-) diff --git a/appconfig/appconfig.go b/appconfig/appconfig.go index b438dc8..57f4188 100755 --- a/appconfig/appconfig.go +++ b/appconfig/appconfig.go @@ -9,6 +9,7 @@ import ( "github.com/chenasraf/sofmani/platform" "github.com/chenasraf/sofmani/utils" "github.com/eschao/config" + "github.com/samber/lo" "gopkg.in/yaml.v3" ) @@ -76,10 +77,7 @@ type AppConfigDefaults struct { // GetCategoryDisplay returns the effective category display mode, defaulting to "border". func (c *AppConfig) GetCategoryDisplay() CategoryDisplayMode { - if c.CategoryDisplay != nil { - return *c.CategoryDisplay - } - return CategoryDisplayBorder + return lo.FromPtrOr(c.CategoryDisplay, CategoryDisplayBorder) } // Environ returns the combined environment variables as a slice of strings. @@ -172,21 +170,9 @@ func tryConfigDir(dir string) string { // GetConfigDesc returns a string slice describing the current configuration. func (c *AppConfig) GetConfigDesc() []string { desc := []string{} - isDebug := false - if c.Debug != nil { - isDebug = *c.Debug - } - checkUpdates := false - if c.CheckUpdates != nil { - checkUpdates = *c.CheckUpdates - } - showSummary := true // default is enabled - if c.Summary != nil { - showSummary = *c.Summary - } - desc = append(desc, fmt.Sprintf("Debug: %t", isDebug)) - desc = append(desc, fmt.Sprintf("CheckUpdates: %t", checkUpdates)) - desc = append(desc, fmt.Sprintf("Summary: %t", showSummary)) + desc = append(desc, fmt.Sprintf("Debug: %t", lo.FromPtrOr(c.Debug, false))) + desc = append(desc, fmt.Sprintf("CheckUpdates: %t", lo.FromPtrOr(c.CheckUpdates, false))) + desc = append(desc, fmt.Sprintf("Summary: %t", lo.FromPtrOr(c.Summary, true))) if c.Env != nil { desc = append(desc, "Environment Variables:") diff --git a/appconfig/appconfig_test.go b/appconfig/appconfig_test.go index b1a50d4..bae1540 100755 --- a/appconfig/appconfig_test.go +++ b/appconfig/appconfig_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/chenasraf/sofmani/platform" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -16,18 +17,18 @@ func TestPlatformMapResolve(t *testing.T) { platform string expected *string }{ - {"MacOS", "darwin", strPtr("macos")}, - {"Linux", "linux", strPtr("linux")}, - {"Windows", "windows", strPtr("windows")}, + {"MacOS", "darwin", lo.ToPtr("macos")}, + {"Linux", "linux", lo.ToPtr("linux")}, + {"Windows", "windows", lo.ToPtr("windows")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { platform.SetOS(tt.platform) pm := platform.PlatformMap[string]{ - MacOS: strPtr("macos"), - Linux: strPtr("linux"), - Windows: strPtr("windows"), + MacOS: lo.ToPtr("macos"), + Linux: lo.ToPtr("linux"), + Windows: lo.ToPtr("windows"), } assert.Equal(t, tt.expected, pm.Resolve()) }) @@ -135,7 +136,3 @@ func TestFindConfigFile(t *testing.T) { assert.NoError(t, os.Chdir(dir)) assert.True(t, strings.HasSuffix(FindConfigFile(), file)) } - -func strPtr(s string) *string { - return &s -} diff --git a/cmd/root.go b/cmd/root.go index 28e9fe9..40c8a34 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" "github.com/chenasraf/sofmani/machine" + "github.com/samber/lo" "github.com/spf13/cobra" ) @@ -153,26 +154,26 @@ func buildCliConfig(cmd *cobra.Command, args []string) *appconfig.AppCliConfig { // Handle debug flag if cmd.Flags().Changed("debug") { - config.Debug = boolPtr(true) + config.Debug = lo.ToPtr(true) } if cmd.Flags().Changed("no-debug") { - config.Debug = boolPtr(false) + config.Debug = lo.ToPtr(false) } // Handle update flag if cmd.Flags().Changed("update") { - config.CheckUpdates = boolPtr(true) + config.CheckUpdates = lo.ToPtr(true) } if cmd.Flags().Changed("no-update") { - config.CheckUpdates = boolPtr(false) + config.CheckUpdates = lo.ToPtr(false) } // Handle summary flag if cmd.Flags().Changed("summary") { - config.Summary = boolPtr(true) + config.Summary = lo.ToPtr(true) } if cmd.Flags().Changed("no-summary") { - config.Summary = boolPtr(false) + config.Summary = lo.ToPtr(false) } // Handle log file flag @@ -201,10 +202,5 @@ func buildCliConfig(cmd *cobra.Command, args []string) *appconfig.AppCliConfig { return config } -// boolPtr returns a pointer to a boolean value. -func boolPtr(b bool) *bool { - return &b -} - // RunMain is set by main.go to run the main application logic. var RunMain func(cliConfig *appconfig.AppCliConfig) diff --git a/installer/apt_installer_test.go b/installer/apt_installer_test.go index de0f08b..7f73b56 100755 --- a/installer/apt_installer_test.go +++ b/installer/apt_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" ) func newAptInstaller(data *appconfig.InstallerData) *AptInstaller { @@ -21,7 +22,7 @@ func TestAptValidation(t *testing.T) { logger.InitLogger(false) aptInstaller := newAptInstaller( &appconfig.InstallerData{ - Name: strPtr("test-apt"), + Name: lo.ToPtr("test-apt"), Type: appconfig.InstallerTypeApt, }, ) @@ -33,7 +34,7 @@ func TestAptGetOpts(t *testing.T) { // Test default opts (no options set) defaultData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeApt, } installer := newAptInstaller(defaultData) @@ -50,7 +51,7 @@ func TestAptGetOpts(t *testing.T) { // Test with flags option flagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeApt, Opts: &map[string]any{ "flags": "-y --no-install-recommends", @@ -64,7 +65,7 @@ func TestAptGetOpts(t *testing.T) { // Test with install_flags option installFlagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeApt, Opts: &map[string]any{ "install_flags": "--no-install-recommends", @@ -78,7 +79,7 @@ func TestAptGetOpts(t *testing.T) { // Test with update_flags option updateFlagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeApt, Opts: &map[string]any{ "update_flags": "--only-upgrade", @@ -92,7 +93,7 @@ func TestAptGetOpts(t *testing.T) { // Test with all flags options combined allFlagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeApt, Opts: &map[string]any{ "flags": "--common", diff --git a/installer/brew_installer_test.go b/installer/brew_installer_test.go index 1d8cdb7..c6cad2a 100755 --- a/installer/brew_installer_test.go +++ b/installer/brew_installer_test.go @@ -8,6 +8,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -25,14 +26,14 @@ func TestBrewValidation(t *testing.T) { // 🟢 Valid: No tap specified (tap is optional) emptyData := &appconfig.InstallerData{ - Name: strPtr("test-brew-valid"), + Name: lo.ToPtr("test-brew-valid"), Type: appconfig.InstallerTypeBrew, } assertNoValidationErrors(t, newTestBrewInstaller(emptyData).Validate()) // 🟢 Valid: Well-formed tap (contains slash, sufficient length) validData := &appconfig.InstallerData{ - Name: strPtr("test-brew-valid-tap"), + Name: lo.ToPtr("test-brew-valid-tap"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{"tap": "valid/tap"}, } @@ -40,7 +41,7 @@ func TestBrewValidation(t *testing.T) { // 🟢 Valid: Tap and cask used together tapCaskData := &appconfig.InstallerData{ - Name: strPtr("test-brew-tap-cask"), + Name: lo.ToPtr("test-brew-tap-cask"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{ "tap": "homebrew/cask-versions", @@ -51,7 +52,7 @@ func TestBrewValidation(t *testing.T) { // 🔴 Invalid: Tap is present but malformed (missing slash or too short) invalidData := &appconfig.InstallerData{ - Name: strPtr("test-brew-invalid-tap"), + Name: lo.ToPtr("test-brew-invalid-tap"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{"tap": "invalid-tap"}, } @@ -85,7 +86,7 @@ func TestBrewGetOpts(t *testing.T) { // Test default opts (no options set) defaultData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, } installer := newTestBrewInstaller(defaultData) @@ -108,7 +109,7 @@ func TestBrewGetOpts(t *testing.T) { // Test with flags option flagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{ "flags": "--verbose --debug", @@ -122,7 +123,7 @@ func TestBrewGetOpts(t *testing.T) { // Test with install_flags option installFlagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{ "install_flags": "--force", @@ -136,7 +137,7 @@ func TestBrewGetOpts(t *testing.T) { // Test with update_flags option updateFlagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{ "update_flags": "--dry-run", @@ -150,7 +151,7 @@ func TestBrewGetOpts(t *testing.T) { // Test with all flags options combined allFlagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{ "tap": "homebrew/core", @@ -250,7 +251,7 @@ func TestBrewGetFullName(t *testing.T) { t.Run("returns name without tap", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, } installer := newTestBrewInstaller(data) @@ -259,7 +260,7 @@ func TestBrewGetFullName(t *testing.T) { t.Run("returns tap/name with tap", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("sofmani"), + Name: lo.ToPtr("sofmani"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{ "tap": "chenasraf/tap", @@ -275,7 +276,7 @@ func TestBrewIsCask(t *testing.T) { t.Run("returns false when cask is not set", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, } installer := newTestBrewInstaller(data) @@ -284,7 +285,7 @@ func TestBrewIsCask(t *testing.T) { t.Run("returns false when cask is false", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("firefox"), + Name: lo.ToPtr("firefox"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{ "cask": false, @@ -296,7 +297,7 @@ func TestBrewIsCask(t *testing.T) { t.Run("returns true when cask is true", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("firefox"), + Name: lo.ToPtr("firefox"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{ "cask": true, @@ -312,7 +313,7 @@ func TestBrewGetBinName(t *testing.T) { t.Run("returns name when bin_name is not set", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, } installer := newTestBrewInstaller(data) @@ -322,7 +323,7 @@ func TestBrewGetBinName(t *testing.T) { t.Run("returns bin_name when set", func(t *testing.T) { binName := "nvim" data := &appconfig.InstallerData{ - Name: strPtr("neovim"), + Name: lo.ToPtr("neovim"), Type: appconfig.InstallerTypeBrew, BinName: &binName, } @@ -333,7 +334,7 @@ func TestBrewGetBinName(t *testing.T) { t.Run("returns name when bin_name is empty", func(t *testing.T) { binName := "" data := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, BinName: &binName, } @@ -347,7 +348,7 @@ func TestBrewGetData(t *testing.T) { t.Run("returns the installer data", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, } installer := newTestBrewInstaller(data) @@ -364,7 +365,7 @@ func TestNewBrewInstaller(t *testing.T) { t.Run("creates installer with config and data", func(t *testing.T) { cfg := &appconfig.AppConfig{} data := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, } installer := NewBrewInstaller(cfg, data) @@ -382,7 +383,7 @@ func TestBrewCheckIsInstalled(t *testing.T) { t.Run("runs custom check when provided", func(t *testing.T) { checkCmd := "true" data := &appconfig.InstallerData{ - Name: strPtr("test-brew"), + Name: lo.ToPtr("test-brew"), Type: appconfig.InstallerTypeBrew, CheckInstalled: &checkCmd, } @@ -396,7 +397,7 @@ func TestBrewCheckIsInstalled(t *testing.T) { t.Run("runs custom check that fails", func(t *testing.T) { checkCmd := "false" data := &appconfig.InstallerData{ - Name: strPtr("test-brew"), + Name: lo.ToPtr("test-brew"), Type: appconfig.InstallerTypeBrew, CheckInstalled: &checkCmd, } @@ -414,7 +415,7 @@ func TestBrewCheckNeedsUpdate(t *testing.T) { t.Run("runs custom check when provided", func(t *testing.T) { checkCmd := "true" // Returns exit code 0, meaning update available data := &appconfig.InstallerData{ - Name: strPtr("test-brew"), + Name: lo.ToPtr("test-brew"), Type: appconfig.InstallerTypeBrew, CheckHasUpdate: &checkCmd, } @@ -428,7 +429,7 @@ func TestBrewCheckNeedsUpdate(t *testing.T) { t.Run("custom check returns false when no update", func(t *testing.T) { checkCmd := "false" // Returns exit code 1, meaning no update data := &appconfig.InstallerData{ - Name: strPtr("test-brew"), + Name: lo.ToPtr("test-brew"), Type: appconfig.InstallerTypeBrew, CheckHasUpdate: &checkCmd, } @@ -445,7 +446,7 @@ func TestBrewGetOptsWrongTypes(t *testing.T) { t.Run("handles wrong type values gracefully", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypeBrew, Opts: &map[string]any{ "tap": 123, // Wrong type diff --git a/installer/cargo_installer_test.go b/installer/cargo_installer_test.go index 4d58777..67ac9cb 100644 --- a/installer/cargo_installer_test.go +++ b/installer/cargo_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" ) func newTestCargoInstaller(data *appconfig.InstallerData) *CargoInstaller { @@ -22,7 +23,7 @@ func TestCargoValidation(t *testing.T) { // 🟢 Valid cargo installer validData := &appconfig.InstallerData{ - Name: strPtr("ripgrep"), + Name: lo.ToPtr("ripgrep"), Type: appconfig.InstallerTypeCargo, } assertNoValidationErrors(t, newTestCargoInstaller(validData).Validate()) @@ -40,7 +41,7 @@ func TestCargoGetOpts(t *testing.T) { // Test default opts (no options set) defaultData := &appconfig.InstallerData{ - Name: strPtr("ripgrep"), + Name: lo.ToPtr("ripgrep"), Type: appconfig.InstallerTypeCargo, } installer := newTestCargoInstaller(defaultData) @@ -57,7 +58,7 @@ func TestCargoGetOpts(t *testing.T) { // Test with flags option flagsData := &appconfig.InstallerData{ - Name: strPtr("ripgrep"), + Name: lo.ToPtr("ripgrep"), Type: appconfig.InstallerTypeCargo, Opts: &map[string]any{ "flags": "--locked", @@ -71,7 +72,7 @@ func TestCargoGetOpts(t *testing.T) { // Test with install_flags option installFlagsData := &appconfig.InstallerData{ - Name: strPtr("ripgrep"), + Name: lo.ToPtr("ripgrep"), Type: appconfig.InstallerTypeCargo, Opts: &map[string]any{ "install_flags": "--features pcre2", @@ -85,7 +86,7 @@ func TestCargoGetOpts(t *testing.T) { // Test with update_flags option updateFlagsData := &appconfig.InstallerData{ - Name: strPtr("ripgrep"), + Name: lo.ToPtr("ripgrep"), Type: appconfig.InstallerTypeCargo, Opts: &map[string]any{ "update_flags": "--force", @@ -99,7 +100,7 @@ func TestCargoGetOpts(t *testing.T) { // Test with all flags options combined allFlagsData := &appconfig.InstallerData{ - Name: strPtr("ripgrep"), + Name: lo.ToPtr("ripgrep"), Type: appconfig.InstallerTypeCargo, Opts: &map[string]any{ "flags": "--common", @@ -125,7 +126,7 @@ func TestCargoGetBinName(t *testing.T) { // Default: uses installer name defaultData := &appconfig.InstallerData{ - Name: strPtr("ripgrep"), + Name: lo.ToPtr("ripgrep"), Type: appconfig.InstallerTypeCargo, } installer := newTestCargoInstaller(defaultData) @@ -135,9 +136,9 @@ func TestCargoGetBinName(t *testing.T) { // Override: uses bin_name binNameData := &appconfig.InstallerData{ - Name: strPtr("ripgrep"), + Name: lo.ToPtr("ripgrep"), Type: appconfig.InstallerTypeCargo, - BinName: strPtr("rg"), + BinName: lo.ToPtr("rg"), } installerWithBinName := newTestCargoInstaller(binNameData) if installerWithBinName.GetBinName() != "rg" { @@ -149,7 +150,7 @@ func TestCargoGetData(t *testing.T) { logger.InitLogger(false) data := &appconfig.InstallerData{ - Name: strPtr("ripgrep"), + Name: lo.ToPtr("ripgrep"), Type: appconfig.InstallerTypeCargo, } installer := newTestCargoInstaller(data) diff --git a/installer/docker_installer_test.go b/installer/docker_installer_test.go index 2cbf2df..ec6e1f9 100755 --- a/installer/docker_installer_test.go +++ b/installer/docker_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" "github.com/stretchr/testify/require" ) @@ -23,17 +24,17 @@ func TestDockerValidation(t *testing.T) { // 🟢 Valid: just name and type validData := &appconfig.InstallerData{ - Name: strPtr("ghcr.io/open-webui/open-webui:main"), + Name: lo.ToPtr("ghcr.io/open-webui/open-webui:main"), Type: appconfig.InstallerTypeDocker, - BinName: strPtr("open-webui"), + BinName: lo.ToPtr("open-webui"), } assertNoValidationErrors(t, newTestDockerInstaller(validData).Validate()) // 🟢 Valid: with flags withFlags := &appconfig.InstallerData{ - Name: strPtr("ghcr.io/open-webui/open-webui:main"), + Name: lo.ToPtr("ghcr.io/open-webui/open-webui:main"), Type: appconfig.InstallerTypeDocker, - BinName: strPtr("open-webui"), + BinName: lo.ToPtr("open-webui"), Opts: &map[string]any{ "flags": "-p 3300:8080 -v open-webui:/data", }, diff --git a/installer/filter.go b/installer/filter.go index a4e54a0..49ef7d3 100755 --- a/installer/filter.go +++ b/installer/filter.go @@ -22,19 +22,14 @@ func FilterInstaller(installer IInstaller, filters []string) bool { return filter[1:], filter[0] == '!' }) - keep := len(positives) == 0 + keep := len(positives) == 0 || lo.SomeBy(positives, func(f string) bool { + return isFilteredIn(installer, f) + }) - for _, f := range positives { - if isFilteredIn(installer, f) { - keep = true - break - } - } - for _, f := range negatives { - if isFilteredIn(installer, f) { - keep = false - break - } + if keep && lo.SomeBy(negatives, func(f string) bool { + return isFilteredIn(installer, f) + }) { + return false } return keep diff --git a/installer/filter_test.go b/installer/filter_test.go index 07400a3..953ca07 100755 --- a/installer/filter_test.go +++ b/installer/filter_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -18,7 +19,7 @@ func TestFilterInstaller(t *testing.T) { { name: "No filters", installer: &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("tag1")}, }, filters: []string{}, expected: true, @@ -26,7 +27,7 @@ func TestFilterInstaller(t *testing.T) { { name: "Positive filter match", installer: &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("tag1")}, }, filters: []string{"test"}, expected: true, @@ -34,7 +35,7 @@ func TestFilterInstaller(t *testing.T) { { name: "Positive filter no match", installer: &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("tag1")}, }, filters: []string{"example"}, expected: false, @@ -42,7 +43,7 @@ func TestFilterInstaller(t *testing.T) { { name: "Negative filter match", installer: &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("tag1")}, }, filters: []string{"!test"}, expected: false, @@ -50,7 +51,7 @@ func TestFilterInstaller(t *testing.T) { { name: "Negative filter no match", installer: &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("tag1")}, }, filters: []string{"!example"}, expected: true, @@ -58,7 +59,7 @@ func TestFilterInstaller(t *testing.T) { { name: "Tag filter match", installer: &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("tag1")}, }, filters: []string{"tag:tag1"}, expected: true, @@ -66,7 +67,7 @@ func TestFilterInstaller(t *testing.T) { { name: "Tag filter no match", installer: &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("tag1")}, }, filters: []string{"tag:tag2"}, expected: false, @@ -74,7 +75,7 @@ func TestFilterInstaller(t *testing.T) { { name: "Type filter match", installer: &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Type: appconfig.InstallerTypeBrew}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Type: appconfig.InstallerTypeBrew}, }, filters: []string{"type:brew"}, expected: true, @@ -82,7 +83,7 @@ func TestFilterInstaller(t *testing.T) { { name: "Type filter no match", installer: &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Type: appconfig.InstallerTypeBrew}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Type: appconfig.InstallerTypeBrew}, }, filters: []string{"type:npm"}, expected: false, @@ -115,28 +116,28 @@ func TestInstallerIsEnabled(t *testing.T) { { name: "Enabled is true", installer: &MockInstaller{ - data: &appconfig.InstallerData{Enabled: strPtr("true")}, + data: &appconfig.InstallerData{Enabled: lo.ToPtr("true")}, }, expected: true, }, { name: "Enabled is false", installer: &MockInstaller{ - data: &appconfig.InstallerData{Enabled: strPtr("false")}, + data: &appconfig.InstallerData{Enabled: lo.ToPtr("false")}, }, expected: false, }, { name: "Enabled is a command that succeeds", installer: &MockInstaller{ - data: &appconfig.InstallerData{Enabled: strPtr("exit 0")}, + data: &appconfig.InstallerData{Enabled: lo.ToPtr("exit 0")}, }, expected: true, }, { name: "Enabled is a command that fails", installer: &MockInstaller{ - data: &appconfig.InstallerData{Enabled: strPtr("exit 1")}, + data: &appconfig.InstallerData{Enabled: lo.ToPtr("exit 1")}, }, expected: false, expectErr: false, @@ -161,7 +162,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Multiple tags - one matches", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1 tag2 tag3")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("tag1 tag2 tag3")}, } result := FilterInstaller(installer, []string{"tag:tag2"}) assert.True(t, result) @@ -169,7 +170,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Multiple tags - none match", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1 tag2 tag3")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("tag1 tag2 tag3")}, } result := FilterInstaller(installer, []string{"tag:tag4"}) assert.False(t, result) @@ -177,7 +178,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Mixed positive and negative filters - positive matches, negative matches too", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("dev prod")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("dev prod")}, } // Should be excluded because negative filter matches result := FilterInstaller(installer, []string{"tag:dev", "!tag:prod"}) @@ -186,7 +187,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Mixed positive and negative filters - only positive matches", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("dev")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("dev")}, } result := FilterInstaller(installer, []string{"tag:dev", "!tag:prod"}) assert.True(t, result) @@ -194,7 +195,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Name substring match", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("my-awesome-tool"), Tags: strPtr("")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("my-awesome-tool"), Tags: lo.ToPtr("")}, } result := FilterInstaller(installer, []string{"awesome"}) assert.True(t, result) @@ -202,7 +203,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Name exact match", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("vim"), Tags: lo.ToPtr("")}, } result := FilterInstaller(installer, []string{"vim"}) assert.True(t, result) @@ -210,7 +211,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Type filter case insensitive", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Type: appconfig.InstallerTypeBrew}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Type: appconfig.InstallerTypeBrew}, } result := FilterInstaller(installer, []string{"type:BREW"}) assert.True(t, result) @@ -218,7 +219,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Multiple positive filters - one matches", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("editor")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("vim"), Tags: lo.ToPtr("editor")}, } result := FilterInstaller(installer, []string{"neovim", "vim", "emacs"}) assert.True(t, result) @@ -226,7 +227,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Multiple positive filters - none match", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("editor")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("vim"), Tags: lo.ToPtr("editor")}, } result := FilterInstaller(installer, []string{"neovim", "emacs", "nano"}) assert.False(t, result) @@ -234,7 +235,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Multiple negative filters - all don't match", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("editor")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("vim"), Tags: lo.ToPtr("editor")}, } result := FilterInstaller(installer, []string{"!neovim", "!emacs"}) assert.True(t, result) @@ -242,7 +243,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Multiple negative filters - one matches", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("editor")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("vim"), Tags: lo.ToPtr("editor")}, } result := FilterInstaller(installer, []string{"!neovim", "!vim"}) assert.False(t, result) @@ -250,7 +251,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Negative type filter", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Type: appconfig.InstallerTypeBrew}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Type: appconfig.InstallerTypeBrew}, } result := FilterInstaller(installer, []string{"!type:npm"}) assert.True(t, result) @@ -258,7 +259,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Negative type filter matches", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Type: appconfig.InstallerTypeBrew}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Type: appconfig.InstallerTypeBrew}, } result := FilterInstaller(installer, []string{"!type:brew"}) assert.False(t, result) @@ -266,7 +267,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Negative tag filter", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("dev")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("dev")}, } result := FilterInstaller(installer, []string{"!tag:prod"}) assert.True(t, result) @@ -274,7 +275,7 @@ func TestFilterInstallerEdgeCases(t *testing.T) { t.Run("Negative tag filter matches", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("dev")}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Tags: lo.ToPtr("dev")}, } result := FilterInstaller(installer, []string{"!tag:dev"}) assert.False(t, result) @@ -286,7 +287,7 @@ func TestInstallerIsEnabledEdgeCases(t *testing.T) { t.Run("Enabled is TRUE (uppercase)", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Enabled: strPtr("TRUE")}, + data: &appconfig.InstallerData{Enabled: lo.ToPtr("TRUE")}, } result, err := InstallerIsEnabled(installer) assert.NoError(t, err) @@ -295,7 +296,7 @@ func TestInstallerIsEnabledEdgeCases(t *testing.T) { t.Run("Enabled is FALSE (uppercase)", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Enabled: strPtr("FALSE")}, + data: &appconfig.InstallerData{Enabled: lo.ToPtr("FALSE")}, } result, err := InstallerIsEnabled(installer) assert.NoError(t, err) @@ -304,7 +305,7 @@ func TestInstallerIsEnabledEdgeCases(t *testing.T) { t.Run("Enabled is True (mixed case)", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Enabled: strPtr("True")}, + data: &appconfig.InstallerData{Enabled: lo.ToPtr("True")}, } result, err := InstallerIsEnabled(installer) assert.NoError(t, err) @@ -313,7 +314,7 @@ func TestInstallerIsEnabledEdgeCases(t *testing.T) { t.Run("Enabled is a command that checks for which", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Enabled: strPtr("which which")}, + data: &appconfig.InstallerData{Enabled: lo.ToPtr("which which")}, } result, err := InstallerIsEnabled(installer) assert.NoError(t, err) @@ -322,7 +323,7 @@ func TestInstallerIsEnabledEdgeCases(t *testing.T) { t.Run("Enabled is a command that checks for nonexistent binary", func(t *testing.T) { installer := &MockInstaller{ - data: &appconfig.InstallerData{Enabled: strPtr("which nonexistent-binary-12345")}, + data: &appconfig.InstallerData{Enabled: lo.ToPtr("which nonexistent-binary-12345")}, } result, err := InstallerIsEnabled(installer) assert.NoError(t, err) diff --git a/installer/git_installer_test.go b/installer/git_installer_test.go index d9909dc..0b3ff9c 100755 --- a/installer/git_installer_test.go +++ b/installer/git_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" ) func newTestGitInstaller(data *appconfig.InstallerData) *GitInstaller { @@ -21,7 +22,7 @@ func TestGitValidation(t *testing.T) { // 🟢 Valid: Both destination and ref are present validData := &appconfig.InstallerData{ - Name: strPtr("test-git-valid"), + Name: lo.ToPtr("test-git-valid"), Type: appconfig.InstallerTypeGit, Opts: &map[string]any{ "destination": "/some/path", @@ -32,7 +33,7 @@ func TestGitValidation(t *testing.T) { // 🟢 Valid: Missing ref missingRefData := &appconfig.InstallerData{ - Name: strPtr("test-git-missing-ref"), + Name: lo.ToPtr("test-git-missing-ref"), Type: appconfig.InstallerTypeGit, Opts: &map[string]any{ "destination": "/some/path", @@ -42,7 +43,7 @@ func TestGitValidation(t *testing.T) { // 🔴 Invalid: Missing destination missingDestData := &appconfig.InstallerData{ - Name: strPtr("test-git-missing-destination"), + Name: lo.ToPtr("test-git-missing-destination"), Type: appconfig.InstallerTypeGit, Opts: &map[string]any{ "ref": "main", @@ -56,7 +57,7 @@ func TestGitGetOpts(t *testing.T) { // Test default opts (only destination set) defaultData := &appconfig.InstallerData{ - Name: strPtr("owner/repo"), + Name: lo.ToPtr("owner/repo"), Type: appconfig.InstallerTypeGit, Opts: &map[string]any{ "destination": "/some/path", @@ -82,7 +83,7 @@ func TestGitGetOpts(t *testing.T) { // Test with flags option flagsData := &appconfig.InstallerData{ - Name: strPtr("owner/repo"), + Name: lo.ToPtr("owner/repo"), Type: appconfig.InstallerTypeGit, Opts: &map[string]any{ "destination": "/some/path", @@ -97,7 +98,7 @@ func TestGitGetOpts(t *testing.T) { // Test with install_flags option (for git clone) installFlagsData := &appconfig.InstallerData{ - Name: strPtr("owner/repo"), + Name: lo.ToPtr("owner/repo"), Type: appconfig.InstallerTypeGit, Opts: &map[string]any{ "destination": "/some/path", @@ -112,7 +113,7 @@ func TestGitGetOpts(t *testing.T) { // Test with update_flags option (for git pull) updateFlagsData := &appconfig.InstallerData{ - Name: strPtr("owner/repo"), + Name: lo.ToPtr("owner/repo"), Type: appconfig.InstallerTypeGit, Opts: &map[string]any{ "destination": "/some/path", @@ -127,7 +128,7 @@ func TestGitGetOpts(t *testing.T) { // Test with all options combined allOptsData := &appconfig.InstallerData{ - Name: strPtr("owner/repo"), + Name: lo.ToPtr("owner/repo"), Type: appconfig.InstallerTypeGit, Opts: &map[string]any{ "destination": "/some/path", diff --git a/installer/github_release_installer_test.go b/installer/github_release_installer_test.go index 9d08bc2..ea7ef64 100755 --- a/installer/github_release_installer_test.go +++ b/installer/github_release_installer_test.go @@ -8,6 +8,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" "github.com/chenasraf/sofmani/platform" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -25,7 +26,7 @@ func TestGitHubReleaseValidation(t *testing.T) { // 🟢 Valid validData := &appconfig.InstallerData{ - Name: strPtr("ghr-valid"), + Name: lo.ToPtr("ghr-valid"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "repository": "owner/repo", @@ -38,7 +39,7 @@ func TestGitHubReleaseValidation(t *testing.T) { // 🔴 Missing repository missingRepo := &appconfig.InstallerData{ - Name: strPtr("ghr-missing-repo"), + Name: lo.ToPtr("ghr-missing-repo"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "destination": "/some/path", @@ -49,7 +50,7 @@ func TestGitHubReleaseValidation(t *testing.T) { // 🔴 Missing download_filename missingDownloadFilename := &appconfig.InstallerData{ - Name: strPtr("ghr-missing-download"), + Name: lo.ToPtr("ghr-missing-download"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "repository": "owner/repo", @@ -60,13 +61,13 @@ func TestGitHubReleaseValidation(t *testing.T) { // 🔴 Empty per-platform download_filename emptyPlatformFilename := &appconfig.InstallerData{ - Name: strPtr("ghr-empty-platform-filename"), + Name: lo.ToPtr("ghr-empty-platform-filename"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "repository": "owner/repo", "destination": "/some/path", "download_filename": map[string]*string{ - string(platform.GetPlatform()): strPtr(""), + string(platform.GetPlatform()): lo.ToPtr(""), }, }, } @@ -74,7 +75,7 @@ func TestGitHubReleaseValidation(t *testing.T) { // 🔴 Invalid strategy invalidStrategy := &appconfig.InstallerData{ - Name: strPtr("ghr-invalid-strategy"), + Name: lo.ToPtr("ghr-invalid-strategy"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "repository": "owner/repo", @@ -91,7 +92,7 @@ func TestGitHubReleaseGetOpts(t *testing.T) { t.Run("parses all options correctly", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "repository": "owner/repo", @@ -110,7 +111,7 @@ func TestGitHubReleaseGetOpts(t *testing.T) { t.Run("handles nil opts", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, Opts: nil, } @@ -124,7 +125,7 @@ func TestGitHubReleaseGetOpts(t *testing.T) { t.Run("handles zip strategy", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "strategy": "zip", @@ -138,7 +139,7 @@ func TestGitHubReleaseGetOpts(t *testing.T) { t.Run("handles none strategy", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "strategy": "none", @@ -157,7 +158,7 @@ func TestGitHubReleaseGetBinName(t *testing.T) { t.Run("returns bin_name when set", func(t *testing.T) { binName := "custom-bin" data := &appconfig.InstallerData{ - Name: strPtr("my-app"), + Name: lo.ToPtr("my-app"), Type: appconfig.InstallerTypeGitHubRelease, BinName: &binName, } @@ -167,7 +168,7 @@ func TestGitHubReleaseGetBinName(t *testing.T) { t.Run("returns base name when bin_name not set", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("owner/my-app"), + Name: lo.ToPtr("owner/my-app"), Type: appconfig.InstallerTypeGitHubRelease, } installer := newTestGitHubReleaseInstaller(data) @@ -181,7 +182,7 @@ func TestGitHubReleaseGetArchiveBinName(t *testing.T) { t.Run("returns archive_bin_name when set", func(t *testing.T) { binName := "cospend" data := &appconfig.InstallerData{ - Name: strPtr("cospend-cli"), + Name: lo.ToPtr("cospend-cli"), Type: appconfig.InstallerTypeGitHubRelease, BinName: &binName, Opts: &map[string]any{ @@ -196,7 +197,7 @@ func TestGitHubReleaseGetArchiveBinName(t *testing.T) { t.Run("falls back to bin_name when archive_bin_name not set", func(t *testing.T) { binName := "custom-bin" data := &appconfig.InstallerData{ - Name: strPtr("my-app"), + Name: lo.ToPtr("my-app"), Type: appconfig.InstallerTypeGitHubRelease, BinName: &binName, } @@ -206,7 +207,7 @@ func TestGitHubReleaseGetArchiveBinName(t *testing.T) { t.Run("falls back to name when neither set", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("my-app"), + Name: lo.ToPtr("my-app"), Type: appconfig.InstallerTypeGitHubRelease, } installer := newTestGitHubReleaseInstaller(data) @@ -219,7 +220,7 @@ func TestGitHubReleaseGetFilename(t *testing.T) { t.Run("returns filename for current platform", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "download_filename": "app.tar.gz", @@ -231,7 +232,7 @@ func TestGitHubReleaseGetFilename(t *testing.T) { t.Run("returns empty string when not set", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{}, } @@ -254,7 +255,7 @@ func TestGitHubReleaseCacheOperations(t *testing.T) { t.Run("UpdateCache writes tag to file", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-cache-app"), + Name: lo.ToPtr("test-cache-app"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "repository": "owner/repo", @@ -276,7 +277,7 @@ func TestGitHubReleaseCacheOperations(t *testing.T) { t.Run("GetCachedTag returns empty for non-existent cache", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("non-existent-app-12345"), + Name: lo.ToPtr("non-existent-app-12345"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "repository": "owner/repo", @@ -293,7 +294,7 @@ func TestGitHubReleaseCacheOperations(t *testing.T) { t.Run("UpdateCache overwrites existing cache", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-overwrite-app"), + Name: lo.ToPtr("test-overwrite-app"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "repository": "owner/repo", @@ -323,7 +324,7 @@ func TestGitHubReleaseGetDestination(t *testing.T) { t.Run("returns destination from opts", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "destination": "/usr/local/bin", @@ -335,7 +336,7 @@ func TestGitHubReleaseGetDestination(t *testing.T) { t.Run("returns current directory when destination not set", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{}, } @@ -350,7 +351,7 @@ func TestGitHubReleaseGetInstallDir(t *testing.T) { t.Run("returns same as destination", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "destination": "/opt/bin", @@ -376,7 +377,7 @@ func TestGitHubReleaseCheckIsInstalled(t *testing.T) { assert.NoError(t, err) data := &appconfig.InstallerData{ - Name: strPtr("myapp"), + Name: lo.ToPtr("myapp"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "destination": tmpDir, @@ -395,7 +396,7 @@ func TestGitHubReleaseCheckIsInstalled(t *testing.T) { defer func() { _ = os.RemoveAll(tmpDir) }() data := &appconfig.InstallerData{ - Name: strPtr("nonexistent-app"), + Name: lo.ToPtr("nonexistent-app"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "destination": tmpDir, @@ -411,7 +412,7 @@ func TestGitHubReleaseCheckIsInstalled(t *testing.T) { t.Run("uses custom check when provided", func(t *testing.T) { checkCmd := "true" data := &appconfig.InstallerData{ - Name: strPtr("myapp"), + Name: lo.ToPtr("myapp"), Type: appconfig.InstallerTypeGitHubRelease, CheckInstalled: &checkCmd, } @@ -429,7 +430,7 @@ func TestGitHubReleaseCheckNeedsUpdate(t *testing.T) { t.Run("uses custom check when provided", func(t *testing.T) { checkCmd := "true" // returns success = update needed data := &appconfig.InstallerData{ - Name: strPtr("myapp"), + Name: lo.ToPtr("myapp"), Type: appconfig.InstallerTypeGitHubRelease, CheckHasUpdate: &checkCmd, } @@ -443,7 +444,7 @@ func TestGitHubReleaseCheckNeedsUpdate(t *testing.T) { t.Run("returns true when no cached tag", func(t *testing.T) { // Use a unique name that won't have a cache file data := &appconfig.InstallerData{ - Name: strPtr("unique-no-cache-app-99999"), + Name: lo.ToPtr("unique-no-cache-app-99999"), Type: appconfig.InstallerTypeGitHubRelease, Opts: &map[string]any{ "repository": "owner/repo", @@ -465,7 +466,7 @@ func TestNewGitHubReleaseInstaller(t *testing.T) { t.Run("creates installer with config and data", func(t *testing.T) { cfg := &appconfig.AppConfig{} data := &appconfig.InstallerData{ - Name: strPtr("test-release"), + Name: lo.ToPtr("test-release"), Type: appconfig.InstallerTypeGitHubRelease, } installer := NewGitHubReleaseInstaller(cfg, data) diff --git a/installer/group_installer_test.go b/installer/group_installer_test.go index 1d223a5..8567708 100755 --- a/installer/group_installer_test.go +++ b/installer/group_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -23,11 +24,11 @@ func TestGroupValidation(t *testing.T) { // 🟢 Valid: one sub-installer validStep := appconfig.InstallerData{ - Name: strPtr("child-installer"), + Name: lo.ToPtr("child-installer"), Type: appconfig.InstallerTypeBrew, } validData := &appconfig.InstallerData{ - Name: strPtr("group-valid"), + Name: lo.ToPtr("group-valid"), Type: appconfig.InstallerTypeGroup, Steps: &[]appconfig.InstallerData{validStep}, } @@ -35,7 +36,7 @@ func TestGroupValidation(t *testing.T) { // 🔴 Invalid: empty steps slice emptySteps := &appconfig.InstallerData{ - Name: strPtr("group-empty"), + Name: lo.ToPtr("group-empty"), Type: appconfig.InstallerTypeGroup, Steps: &[]appconfig.InstallerData{}, } @@ -43,7 +44,7 @@ func TestGroupValidation(t *testing.T) { // 🔴 Invalid: nil steps nilSteps := &appconfig.InstallerData{ - Name: strPtr("group-nil"), + Name: lo.ToPtr("group-nil"), Type: appconfig.InstallerTypeGroup, Steps: nil, } @@ -55,7 +56,7 @@ func TestGroupGetData(t *testing.T) { t.Run("returns the installer data", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("group-test"), + Name: lo.ToPtr("group-test"), Type: appconfig.InstallerTypeGroup, } installer := newTestGroupInstaller(data) @@ -71,7 +72,7 @@ func TestGroupGetOpts(t *testing.T) { t.Run("returns empty opts", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("group-test"), + Name: lo.ToPtr("group-test"), Type: appconfig.InstallerTypeGroup, } installer := newTestGroupInstaller(data) @@ -86,7 +87,7 @@ func TestGroupGetBinName(t *testing.T) { t.Run("returns name when bin_name is not set", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("my-group"), + Name: lo.ToPtr("my-group"), Type: appconfig.InstallerTypeGroup, } installer := newTestGroupInstaller(data) @@ -96,7 +97,7 @@ func TestGroupGetBinName(t *testing.T) { t.Run("returns bin_name when set", func(t *testing.T) { binName := "custom-bin" data := &appconfig.InstallerData{ - Name: strPtr("my-group"), + Name: lo.ToPtr("my-group"), Type: appconfig.InstallerTypeGroup, BinName: &binName, } @@ -107,7 +108,7 @@ func TestGroupGetBinName(t *testing.T) { t.Run("returns name when bin_name is empty", func(t *testing.T) { binName := "" data := &appconfig.InstallerData{ - Name: strPtr("my-group"), + Name: lo.ToPtr("my-group"), Type: appconfig.InstallerTypeGroup, BinName: &binName, } @@ -122,7 +123,7 @@ func TestGroupCheckIsInstalled(t *testing.T) { t.Run("runs custom check when provided", func(t *testing.T) { checkCmd := "true" data := &appconfig.InstallerData{ - Name: strPtr("group-test"), + Name: lo.ToPtr("group-test"), Type: appconfig.InstallerTypeGroup, CheckInstalled: &checkCmd, } @@ -136,7 +137,7 @@ func TestGroupCheckIsInstalled(t *testing.T) { t.Run("runs custom check that fails", func(t *testing.T) { checkCmd := "false" data := &appconfig.InstallerData{ - Name: strPtr("group-test"), + Name: lo.ToPtr("group-test"), Type: appconfig.InstallerTypeGroup, CheckInstalled: &checkCmd, } @@ -153,7 +154,7 @@ func TestGroupCheckNeedsUpdate(t *testing.T) { t.Run("returns true when no custom check", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("group-test"), + Name: lo.ToPtr("group-test"), Type: appconfig.InstallerTypeGroup, } installer := newTestGroupInstaller(data) @@ -166,7 +167,7 @@ func TestGroupCheckNeedsUpdate(t *testing.T) { t.Run("runs custom check when provided", func(t *testing.T) { checkCmd := "false" // Returns exit code 1, meaning no update data := &appconfig.InstallerData{ - Name: strPtr("group-test"), + Name: lo.ToPtr("group-test"), Type: appconfig.InstallerTypeGroup, CheckHasUpdate: &checkCmd, } @@ -184,7 +185,7 @@ func TestNewGroupInstaller(t *testing.T) { t.Run("creates installer with config and data", func(t *testing.T) { cfg := &appconfig.AppConfig{} data := &appconfig.InstallerData{ - Name: strPtr("group-test"), + Name: lo.ToPtr("group-test"), Type: appconfig.InstallerTypeGroup, } installer := NewGroupInstaller(cfg, data) @@ -200,15 +201,15 @@ func TestGroupValidationWithMultipleSteps(t *testing.T) { t.Run("valid with multiple steps", func(t *testing.T) { step1 := appconfig.InstallerData{ - Name: strPtr("step1"), + Name: lo.ToPtr("step1"), Type: appconfig.InstallerTypeBrew, } step2 := appconfig.InstallerData{ - Name: strPtr("step2"), + Name: lo.ToPtr("step2"), Type: appconfig.InstallerTypeShell, } data := &appconfig.InstallerData{ - Name: strPtr("group-multi"), + Name: lo.ToPtr("group-multi"), Type: appconfig.InstallerTypeGroup, Steps: &[]appconfig.InstallerData{step1, step2}, } diff --git a/installer/installer_test.go b/installer/installer_test.go index dfbf674..007543e 100755 --- a/installer/installer_test.go +++ b/installer/installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -32,7 +33,7 @@ func TestInstallerWithDefaults(t *testing.T) { func TestRunInstaller(t *testing.T) { config := &appconfig.AppConfig{} mockInstaller := &MockInstaller{ - data: &appconfig.InstallerData{Name: strPtr("test"), Type: appconfig.InstallerTypeBrew}, + data: &appconfig.InstallerData{Name: lo.ToPtr("test"), Type: appconfig.InstallerTypeBrew}, isInstalled: false, } result, err := RunInstaller(config, mockInstaller) diff --git a/installer/manifest_installer_test.go b/installer/manifest_installer_test.go index 64a7c6a..76c67ab 100755 --- a/installer/manifest_installer_test.go +++ b/installer/manifest_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -23,7 +24,7 @@ func TestManifestValidation(t *testing.T) { // 🟢 Valid validData := &appconfig.InstallerData{ - Name: strPtr("manifest-valid"), + Name: lo.ToPtr("manifest-valid"), Type: appconfig.InstallerTypeManifest, Opts: &map[string]any{ "source": "https://example.com/repo.git", @@ -35,7 +36,7 @@ func TestManifestValidation(t *testing.T) { // 🔴 Missing source missingSource := &appconfig.InstallerData{ - Name: strPtr("manifest-missing-source"), + Name: lo.ToPtr("manifest-missing-source"), Type: appconfig.InstallerTypeManifest, Opts: &map[string]any{ "path": "some/path", @@ -45,7 +46,7 @@ func TestManifestValidation(t *testing.T) { // 🔴 Missing path missingPath := &appconfig.InstallerData{ - Name: strPtr("manifest-missing-path"), + Name: lo.ToPtr("manifest-missing-path"), Type: appconfig.InstallerTypeManifest, Opts: &map[string]any{ "source": "https://example.com/repo.git", @@ -55,7 +56,7 @@ func TestManifestValidation(t *testing.T) { // 🔴 Empty ref (not nil, just empty) emptyRef := &appconfig.InstallerData{ - Name: strPtr("manifest-empty-ref"), + Name: lo.ToPtr("manifest-empty-ref"), Type: appconfig.InstallerTypeManifest, Opts: &map[string]any{ "source": "https://example.com/repo.git", @@ -67,7 +68,7 @@ func TestManifestValidation(t *testing.T) { // 🔴 Nil opts nilOpts := &appconfig.InstallerData{ - Name: strPtr("manifest-nil-opts"), + Name: lo.ToPtr("manifest-nil-opts"), Type: appconfig.InstallerTypeManifest, Opts: nil, } @@ -79,7 +80,7 @@ func TestManifestGetOpts(t *testing.T) { t.Run("returns all opts when set", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, Opts: &map[string]any{ "source": "https://github.com/user/repo.git", @@ -100,7 +101,7 @@ func TestManifestGetOpts(t *testing.T) { t.Run("returns nil fields when opts is nil", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, Opts: nil, } @@ -114,7 +115,7 @@ func TestManifestGetOpts(t *testing.T) { t.Run("handles partial opts", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, Opts: &map[string]any{ "source": "https://github.com/user/repo.git", @@ -131,7 +132,7 @@ func TestManifestGetOpts(t *testing.T) { t.Run("handles wrong type values gracefully", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, Opts: &map[string]any{ "source": 123, // Wrong type @@ -154,7 +155,7 @@ func TestManifestGetData(t *testing.T) { t.Run("returns the installer data", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, } installer := newTestManifestInstaller(data) @@ -170,7 +171,7 @@ func TestManifestCheckIsInstalled(t *testing.T) { t.Run("returns false when no custom check", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, } installer := newTestManifestInstaller(data) @@ -183,7 +184,7 @@ func TestManifestCheckIsInstalled(t *testing.T) { t.Run("runs custom check when provided", func(t *testing.T) { checkCmd := "true" data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, CheckInstalled: &checkCmd, } @@ -200,7 +201,7 @@ func TestManifestCheckNeedsUpdate(t *testing.T) { t.Run("returns true when no custom check", func(t *testing.T) { data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, } installer := newTestManifestInstaller(data) @@ -213,7 +214,7 @@ func TestManifestCheckNeedsUpdate(t *testing.T) { t.Run("runs custom check when provided", func(t *testing.T) { checkCmd := "false" // Returns exit code 1, meaning no update data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, CheckHasUpdate: &checkCmd, } @@ -231,7 +232,7 @@ func TestNewManifestInstaller(t *testing.T) { t.Run("creates installer with config and data", func(t *testing.T) { cfg := &appconfig.AppConfig{} data := &appconfig.InstallerData{ - Name: strPtr("manifest-test"), + Name: lo.ToPtr("manifest-test"), Type: appconfig.InstallerTypeManifest, } installer := NewManifestInstaller(cfg, data) diff --git a/installer/npm_installer_test.go b/installer/npm_installer_test.go index 39a7048..94972dd 100755 --- a/installer/npm_installer_test.go +++ b/installer/npm_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" ) func newTestNpmInstaller(data *appconfig.InstallerData) *NpmInstaller { @@ -23,7 +24,7 @@ func TestNpmValidation(t *testing.T) { // 🟢 Valid npm installer validData := &appconfig.InstallerData{ - Name: strPtr("some-npm-package"), + Name: lo.ToPtr("some-npm-package"), Type: appconfig.InstallerTypeNpm, } assertNoValidationErrors(t, newTestNpmInstaller(validData).Validate()) @@ -41,7 +42,7 @@ func TestNpmGetOpts(t *testing.T) { // Test default opts (no options set) defaultData := &appconfig.InstallerData{ - Name: strPtr("prettier"), + Name: lo.ToPtr("prettier"), Type: appconfig.InstallerTypeNpm, } installer := newTestNpmInstaller(defaultData) @@ -58,7 +59,7 @@ func TestNpmGetOpts(t *testing.T) { // Test with flags option flagsData := &appconfig.InstallerData{ - Name: strPtr("prettier"), + Name: lo.ToPtr("prettier"), Type: appconfig.InstallerTypeNpm, Opts: &map[string]any{ "flags": "--legacy-peer-deps", @@ -72,7 +73,7 @@ func TestNpmGetOpts(t *testing.T) { // Test with install_flags option installFlagsData := &appconfig.InstallerData{ - Name: strPtr("prettier"), + Name: lo.ToPtr("prettier"), Type: appconfig.InstallerTypeNpm, Opts: &map[string]any{ "install_flags": "--save-exact", @@ -86,7 +87,7 @@ func TestNpmGetOpts(t *testing.T) { // Test with update_flags option updateFlagsData := &appconfig.InstallerData{ - Name: strPtr("prettier"), + Name: lo.ToPtr("prettier"), Type: appconfig.InstallerTypeNpm, Opts: &map[string]any{ "update_flags": "--force", @@ -100,7 +101,7 @@ func TestNpmGetOpts(t *testing.T) { // Test with all flags options combined allFlagsData := &appconfig.InstallerData{ - Name: strPtr("prettier"), + Name: lo.ToPtr("prettier"), Type: appconfig.InstallerTypeNpm, Opts: &map[string]any{ "flags": "--common", diff --git a/installer/pacman_installer_test.go b/installer/pacman_installer_test.go index c04394d..caa9e23 100755 --- a/installer/pacman_installer_test.go +++ b/installer/pacman_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" ) func newTestPacmanInstaller(data *appconfig.InstallerData) *PacmanInstaller { @@ -34,14 +35,14 @@ func TestPacmanValidation(t *testing.T) { // Valid pacman installer validPacmanData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypePacman, } assertNoValidationErrors(t, newTestPacmanInstaller(validPacmanData).Validate()) // Valid yay installer validYayData := &appconfig.InstallerData{ - Name: strPtr("visual-studio-code-bin"), + Name: lo.ToPtr("visual-studio-code-bin"), Type: appconfig.InstallerTypeYay, } assertNoValidationErrors(t, newTestYayInstaller(validYayData).Validate()) @@ -59,7 +60,7 @@ func TestPacmanGetBinName(t *testing.T) { // Test default bin name (uses package name) defaultBinData := &appconfig.InstallerData{ - Name: strPtr("neovim"), + Name: lo.ToPtr("neovim"), Type: appconfig.InstallerTypePacman, } installer := newTestPacmanInstaller(defaultBinData) @@ -70,7 +71,7 @@ func TestPacmanGetBinName(t *testing.T) { // Test custom bin name customBinName := "nvim" customBinData := &appconfig.InstallerData{ - Name: strPtr("neovim"), + Name: lo.ToPtr("neovim"), Type: appconfig.InstallerTypePacman, BinName: &customBinName, } @@ -85,7 +86,7 @@ func TestPacmanGetOpts(t *testing.T) { // Test default opts (no options set) defaultData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypePacman, } installer := newTestPacmanInstaller(defaultData) @@ -105,7 +106,7 @@ func TestPacmanGetOpts(t *testing.T) { // Test with needed option set to true neededData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypePacman, Opts: &map[string]any{ "needed": true, @@ -119,7 +120,7 @@ func TestPacmanGetOpts(t *testing.T) { // Test with needed option set to false notNeededData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypePacman, Opts: &map[string]any{ "needed": false, @@ -133,7 +134,7 @@ func TestPacmanGetOpts(t *testing.T) { // Test with flags option flagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypePacman, Opts: &map[string]any{ "flags": "--asdeps --overwrite '*'", @@ -147,7 +148,7 @@ func TestPacmanGetOpts(t *testing.T) { // Test with install_flags option installFlagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypePacman, Opts: &map[string]any{ "install_flags": "--asdeps", @@ -161,7 +162,7 @@ func TestPacmanGetOpts(t *testing.T) { // Test with update_flags option updateFlagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypePacman, Opts: &map[string]any{ "update_flags": "--ignore vim", @@ -175,7 +176,7 @@ func TestPacmanGetOpts(t *testing.T) { // Test with all flags options combined allFlagsData := &appconfig.InstallerData{ - Name: strPtr("vim"), + Name: lo.ToPtr("vim"), Type: appconfig.InstallerTypePacman, Opts: &map[string]any{ "flags": "--common", diff --git a/installer/pipx_installer_test.go b/installer/pipx_installer_test.go index 81ccba5..b655451 100755 --- a/installer/pipx_installer_test.go +++ b/installer/pipx_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" ) func newTestPipxInstaller(data *appconfig.InstallerData) *PipxInstaller { @@ -22,7 +23,7 @@ func TestPipxValidation(t *testing.T) { // 🟢 Valid pipx installer validData := &appconfig.InstallerData{ - Name: strPtr("some-pipx-package"), + Name: lo.ToPtr("some-pipx-package"), Type: appconfig.InstallerTypePipx, } assertNoValidationErrors(t, newTestPipxInstaller(validData).Validate()) @@ -40,7 +41,7 @@ func TestPipxGetOpts(t *testing.T) { // Test default opts (no options set) defaultData := &appconfig.InstallerData{ - Name: strPtr("black"), + Name: lo.ToPtr("black"), Type: appconfig.InstallerTypePipx, } installer := newTestPipxInstaller(defaultData) @@ -57,7 +58,7 @@ func TestPipxGetOpts(t *testing.T) { // Test with flags option flagsData := &appconfig.InstallerData{ - Name: strPtr("black"), + Name: lo.ToPtr("black"), Type: appconfig.InstallerTypePipx, Opts: &map[string]any{ "flags": "--verbose", @@ -71,7 +72,7 @@ func TestPipxGetOpts(t *testing.T) { // Test with install_flags option installFlagsData := &appconfig.InstallerData{ - Name: strPtr("black"), + Name: lo.ToPtr("black"), Type: appconfig.InstallerTypePipx, Opts: &map[string]any{ "install_flags": "--python python3.11", @@ -85,7 +86,7 @@ func TestPipxGetOpts(t *testing.T) { // Test with update_flags option updateFlagsData := &appconfig.InstallerData{ - Name: strPtr("black"), + Name: lo.ToPtr("black"), Type: appconfig.InstallerTypePipx, Opts: &map[string]any{ "update_flags": "--force", @@ -99,7 +100,7 @@ func TestPipxGetOpts(t *testing.T) { // Test with all flags options combined allFlagsData := &appconfig.InstallerData{ - Name: strPtr("black"), + Name: lo.ToPtr("black"), Type: appconfig.InstallerTypePipx, Opts: &map[string]any{ "flags": "--common", diff --git a/installer/rsync_installer_test.go b/installer/rsync_installer_test.go index 5652483..d347e7e 100755 --- a/installer/rsync_installer_test.go +++ b/installer/rsync_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" ) func newTestRsyncInstaller(data *appconfig.InstallerData) *RsyncInstaller { @@ -22,7 +23,7 @@ func TestRsyncValidation(t *testing.T) { // 🟢 Valid rsync config validData := &appconfig.InstallerData{ - Name: strPtr("rsync-valid"), + Name: lo.ToPtr("rsync-valid"), Type: appconfig.InstallerTypeRsync, Opts: &map[string]any{ "source": "/path/from", @@ -34,7 +35,7 @@ func TestRsyncValidation(t *testing.T) { // 🔴 Missing source missingSource := &appconfig.InstallerData{ - Name: strPtr("rsync-missing-source"), + Name: lo.ToPtr("rsync-missing-source"), Type: appconfig.InstallerTypeRsync, Opts: &map[string]any{ "destination": "/path/to", @@ -44,7 +45,7 @@ func TestRsyncValidation(t *testing.T) { // 🔴 Missing destination missingDest := &appconfig.InstallerData{ - Name: strPtr("rsync-missing-destination"), + Name: lo.ToPtr("rsync-missing-destination"), Type: appconfig.InstallerTypeRsync, Opts: &map[string]any{ "source": "/path/from", @@ -55,7 +56,7 @@ func TestRsyncValidation(t *testing.T) { // 🔴 Empty flags string emptyFlags := &appconfig.InstallerData{ - Name: strPtr("rsync-empty-flags"), + Name: lo.ToPtr("rsync-empty-flags"), Type: appconfig.InstallerTypeRsync, Opts: &map[string]any{ "source": "/path/from", diff --git a/installer/shell_installer_test.go b/installer/shell_installer_test.go index 9e81847..52ca56b 100755 --- a/installer/shell_installer_test.go +++ b/installer/shell_installer_test.go @@ -5,6 +5,7 @@ import ( "github.com/chenasraf/sofmani/appconfig" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" ) func newTestShellInstaller(data *appconfig.InstallerData) *ShellInstaller { @@ -20,7 +21,7 @@ func TestShellValidation(t *testing.T) { // 🟢 Valid shell config validData := &appconfig.InstallerData{ - Name: strPtr("shell-valid"), + Name: lo.ToPtr("shell-valid"), Type: appconfig.InstallerTypeShell, Opts: &map[string]any{ "command": "echo install", @@ -31,7 +32,7 @@ func TestShellValidation(t *testing.T) { // 🔴 Missing command missingCommand := &appconfig.InstallerData{ - Name: strPtr("shell-missing-command"), + Name: lo.ToPtr("shell-missing-command"), Type: appconfig.InstallerTypeShell, Opts: &map[string]any{ "update_command": "echo update", @@ -41,7 +42,7 @@ func TestShellValidation(t *testing.T) { // 🟢 Valid - missing update_command missingUpdate := &appconfig.InstallerData{ - Name: strPtr("shell-missing-update"), + Name: lo.ToPtr("shell-missing-update"), Type: appconfig.InstallerTypeShell, Opts: &map[string]any{ "command": "echo install", @@ -51,7 +52,7 @@ func TestShellValidation(t *testing.T) { // 🔴 Missing both missingBoth := &appconfig.InstallerData{ - Name: strPtr("shell-missing-both"), + Name: lo.ToPtr("shell-missing-both"), Type: appconfig.InstallerTypeShell, Opts: &map[string]any{}, } diff --git a/installer/testutils.go b/installer/testutils.go index 82777f4..b6dd013 100755 --- a/installer/testutils.go +++ b/installer/testutils.go @@ -70,11 +70,6 @@ func (m *MockInstaller) GetTemplateVars() *TemplateVars { return m.templateVars } -// strPtr returns a pointer to the given string. -func strPtr(s string) *string { - return &s -} - // simulateBrewCheck simulates parsing output from `brew outdated --json` // along with handling the exit code semantics. diff --git a/machine/machine.go b/machine/machine.go index f98556b..794ed81 100755 --- a/machine/machine.go +++ b/machine/machine.go @@ -1,5 +1,7 @@ package machine +import "github.com/samber/lo" + // Machines defines which machines a configuration applies to. type Machines struct { // Only specifies a list of machine IDs or aliases where the configuration should apply. @@ -28,20 +30,12 @@ func (m *Machines) GetShouldRunOnMachine(machineID string, aliases map[string]st // containsMachineID checks if the machine ID is in the list, resolving aliases first. func containsMachineID(list []string, machineID string, aliases map[string]string) bool { - for _, entry := range list { - // First, try to resolve as an alias + return lo.SomeBy(list, func(entry string) bool { if aliases != nil { if resolvedID, ok := aliases[entry]; ok { - if resolvedID == machineID { - return true - } - continue + return resolvedID == machineID } } - // Fall back to treating entry as a literal machine ID - if entry == machineID { - return true - } - } - return false + return entry == machineID + }) } diff --git a/platform/platform.go b/platform/platform.go index 8e3e0bc..f439a4d 100755 --- a/platform/platform.go +++ b/platform/platform.go @@ -4,6 +4,8 @@ import ( "fmt" "runtime" "slices" + + "github.com/samber/lo" ) var osValue string = runtime.GOOS // osValue stores the current operating system. @@ -179,16 +181,11 @@ func (p *Platforms) GetShouldRunOnOS(curOS Platform) bool { return true } -// strPtr returns a pointer to a string. -func strPtr(s string) *string { - return &s -} - // DockerOSMap is a PlatformMap that defines the Docker OS for each platform. var DockerOSMap = PlatformMap[string]{ - MacOS: strPtr("linux"), - Linux: strPtr("linux"), - Windows: strPtr("windows"), + MacOS: lo.ToPtr("linux"), + Linux: lo.ToPtr("linux"), + Windows: lo.ToPtr("windows"), } // ParsePlatformSingleValue creates a new PlatformMap with the value for all platforms diff --git a/platform/platform_test.go b/platform/platform_test.go index f0c079e..5e14b7c 100755 --- a/platform/platform_test.go +++ b/platform/platform_test.go @@ -4,6 +4,7 @@ import ( "runtime" "testing" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -171,16 +172,16 @@ func TestPlatformMapResolve(t *testing.T) { t.Run("returns nil when platform value not set", func(t *testing.T) { SetOS("darwin") - pm := &PlatformMap[string]{Linux: strPtr("linux-only")} + pm := &PlatformMap[string]{Linux: lo.ToPtr("linux-only")} assert.Nil(t, pm.Resolve()) }) t.Run("returns nil for unknown OS", func(t *testing.T) { SetOS("freebsd") pm := &PlatformMap[string]{ - MacOS: strPtr("mac"), - Linux: strPtr("linux"), - Windows: strPtr("windows"), + MacOS: lo.ToPtr("mac"), + Linux: lo.ToPtr("linux"), + Windows: lo.ToPtr("windows"), } assert.Nil(t, pm.Resolve()) }) @@ -192,15 +193,15 @@ func TestPlatformMapResolveWithFallback(t *testing.T) { t.Run("returns primary value when set", func(t *testing.T) { SetOS("darwin") - primary := &PlatformMap[string]{MacOS: strPtr("primary")} - fallback := PlatformMap[string]{MacOS: strPtr("fallback")} + primary := &PlatformMap[string]{MacOS: lo.ToPtr("primary")} + fallback := PlatformMap[string]{MacOS: lo.ToPtr("fallback")} assert.Equal(t, "primary", primary.ResolveWithFallback(fallback)) }) t.Run("returns fallback value when primary not set", func(t *testing.T) { SetOS("darwin") - primary := &PlatformMap[string]{Linux: strPtr("linux-only")} - fallback := PlatformMap[string]{MacOS: strPtr("fallback")} + primary := &PlatformMap[string]{Linux: lo.ToPtr("linux-only")} + fallback := PlatformMap[string]{MacOS: lo.ToPtr("fallback")} assert.Equal(t, "fallback", primary.ResolveWithFallback(fallback)) }) } diff --git a/summary/summary.go b/summary/summary.go index 5b8fbf9..4d85547 100755 --- a/summary/summary.go +++ b/summary/summary.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/chenasraf/sofmani/logger" + "github.com/samber/lo" ) // Action represents the action taken for an installer. @@ -85,12 +86,9 @@ func (s *Summary) Print() { // collectByAction returns all results (including nested) that match the given action. func (s *Summary) collectByAction(action Action) []InstallResult { - var results []InstallResult - for _, r := range s.results { - collected := collectResultsByAction(r, action) - results = append(results, collected...) - } - return results + return lo.FlatMap(s.results, func(r InstallResult, _ int) []InstallResult { + return collectResultsByAction(r, action) + }) } // isContainerType returns true if the installer type is a container (group/manifest). @@ -179,19 +177,12 @@ func filterChildrenByAction(children []InstallResult, action Action) []InstallRe // hasChildrenWithAction checks if any children (recursively) match the action. func hasChildrenWithAction(children []InstallResult, action Action) bool { - for _, child := range children { - // Skip children that should be excluded from summary + return lo.SomeBy(children, func(child InstallResult) bool { if shouldSkipSummary(child, action) { - continue + return false } - if child.Action == action { - return true - } - if hasChildrenWithAction(child.Children, action) { - return true - } - } - return false + return child.Action == action || hasChildrenWithAction(child.Children, action) + }) } // printResult prints a single result with the given indentation level. diff --git a/utils/env.go b/utils/env.go index 8b199d5..5cd17a2 100755 --- a/utils/env.go +++ b/utils/env.go @@ -4,6 +4,8 @@ import ( "fmt" "maps" "strings" + + "github.com/samber/lo" ) // ResolveEnvPaths takes one or more slices of environment variable strings (e.g., "KEY=VALUE"), @@ -62,11 +64,9 @@ func EnvSliceAsMap(env []string) map[string]string { // EnvMapAsSlice converts a map of environment variables to a slice of "KEY=VALUE" strings. func EnvMapAsSlice(env map[string]string) []string { - out := []string{} - for k, v := range env { - out = append(out, fmt.Sprintf("%s=%s", k, v)) - } - return out + return lo.MapToSlice(env, func(k string, v string) string { + return fmt.Sprintf("%s=%s", k, v) + }) } // mergeEnvs helper function to merge a source slice of env strings into a target map (represented as a slice).