• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nightconcept / almandine / 14997931481

13 May 2025 01:29PM UTC coverage: 67.93% (+0.05%) from 67.885%
14997931481

Pull #32

github

nightconcept
fix: remove.go cyclomatic complexity
Pull Request #32: fix: install.sh and cyclomatic complexities

367 of 516 new or added lines in 5 files covered. (71.12%)

10 existing lines in 4 files now uncovered.

968 of 1425 relevant lines covered (67.93%)

3.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.07
/internal/cli/remove/remove.go
1
// Package remove handles project dependency removal operations
2
package remove
3

4
import (
5
        "fmt"
6
        "io"
7
        "os"
8
        "path/filepath"
9
        "strings"
10
        "time"
11

12
        "github.com/fatih/color"
13
        "github.com/nightconcept/almandine/internal/core/config"
14
        "github.com/nightconcept/almandine/internal/core/lockfile"
15
        "github.com/nightconcept/almandine/internal/core/project"
16
        "github.com/nightconcept/almandine/internal/core/source"
17
        "github.com/urfave/cli/v2"
18
)
19

20
func isDirEmpty(path string) (bool, error) {
2✔
21
        entries, err := os.ReadDir(path)
2✔
22
        if err != nil {
2✔
23
                return false, fmt.Errorf("failed to read directory %s: %w", path, err)
×
24
        }
×
25
        return len(entries) == 0, nil
2✔
26
}
27

28
func loadProjectConfigAndValidate(depName string) (proj *project.Project, depDetails project.Dependency, err error) {
6✔
29
        proj, err = config.LoadProjectToml(".")
6✔
30
        if err != nil {
7✔
31
                return nil, project.Dependency{}, fmt.Errorf("failed to load %s: %w", config.ProjectTomlName, err)
1✔
32
        }
1✔
33

34
        if len(proj.Dependencies) == 0 {
6✔
35
                return proj, project.Dependency{}, fmt.Errorf("no dependencies found in %s", config.ProjectTomlName)
1✔
36
        }
1✔
37

38
        depDetails, ok := proj.Dependencies[depName]
4✔
39
        if !ok {
5✔
40
                return proj, project.Dependency{}, fmt.Errorf("dependency '%s' not found in %s", depName, config.ProjectTomlName)
1✔
41
        }
1✔
42
        return proj, depDetails, nil
3✔
43
}
44

45
func updateManifest(proj *project.Project, depName string) error {
3✔
46
        delete(proj.Dependencies, depName)
3✔
47
        if err := config.WriteProjectToml(".", proj); err != nil {
3✔
NEW
48
                return fmt.Errorf("failed to update %s: %w", config.ProjectTomlName, err)
×
NEW
49
        }
×
50
        return nil
3✔
51
}
52

53
func deleteDependencyFileAndCleanup(errWriter io.Writer, dependencyPath string) (fileDeleted bool) {
3✔
54
        if err := os.Remove(dependencyPath); err != nil {
4✔
55
                if !os.IsNotExist(err) {
1✔
NEW
56
                        _, _ = fmt.Fprintf(errWriter, "Warning: Failed to delete dependency file '%s': %v. Manifest updated.\n", dependencyPath, err)
×
NEW
57
                }
×
58
                return false
1✔
59
        }
60

61
        fileDeleted = true
2✔
62
        currentDir := filepath.Dir(dependencyPath)
2✔
63
        projectRootAbs, errAbs := filepath.Abs(".")
2✔
64
        if errAbs != nil {
2✔
NEW
65
                _, _ = fmt.Fprintf(errWriter, "Warning: Could not determine project root absolute path: %v. Skipping directory cleanup.\n", errAbs)
×
NEW
66
                return fileDeleted
×
NEW
67
        }
×
68

69
        // Recursively clean up empty parent directories up to project root
70
        for {
5✔
71
                absCurrentDir, errLoopAbs := filepath.Abs(currentDir)
3✔
72
                if errLoopAbs != nil {
3✔
NEW
73
                        _, _ = fmt.Fprintf(errWriter, "Warning: Could not get absolute path for '%s': %v. Stopping directory cleanup.\n", currentDir, errLoopAbs)
×
NEW
74
                        break
×
75
                }
76
                // Stop if currentDir is project root, or if its parent is itself (e.g. "/" or "C:\"), or if it's "."
77
                if absCurrentDir == projectRootAbs || filepath.Dir(absCurrentDir) == absCurrentDir || currentDir == "." || currentDir == "" {
4✔
78
                        break
1✔
79
                }
80
                empty, errEmpty := isDirEmpty(currentDir)
2✔
81
                if errEmpty != nil {
2✔
NEW
82
                        _, _ = fmt.Fprintf(errWriter, "Warning: Could not check if directory '%s' is empty: %v. Stopping directory cleanup.\n", currentDir, errEmpty)
×
NEW
83
                        break
×
84
                }
85
                if !empty {
3✔
86
                        break
1✔
87
                }
88
                if errRemoveDir := os.Remove(currentDir); errRemoveDir != nil {
1✔
NEW
89
                        _, _ = fmt.Fprintf(errWriter, "Warning: Failed to remove empty directory '%s': %v. Stopping directory cleanup.\n", currentDir, errRemoveDir)
×
NEW
90
                        break
×
91
                }
92
                currentDir = filepath.Dir(currentDir)
1✔
93
        }
94
        return fileDeleted
2✔
95
}
96

97
func updateLockfile(errWriter io.Writer, depName string) (lockfileUpdated bool, lockfileLoadErr error) {
3✔
98
        lf, err := lockfile.Load(".")
3✔
99
        if err != nil {
3✔
NEW
100
                _, _ = fmt.Fprintf(errWriter, "Warning: Failed to load %s: %v. Manifest and file processed.\n", lockfile.LockfileName, err)
×
NEW
101
                return false, err
×
NEW
102
        }
×
103

104
        if lf.Package != nil {
6✔
105
                if _, depInLock := lf.Package[depName]; depInLock {
5✔
106
                        delete(lf.Package, depName)
2✔
107
                        if errSaveLock := lockfile.Save(".", lf); errSaveLock != nil {
2✔
NEW
108
                                _, _ = fmt.Fprintf(errWriter, "Warning: Failed to update %s: %v. Manifest and file processed.\n", lockfile.LockfileName, errSaveLock)
×
NEW
109
                                return false, err // Return original load error for note consistency
×
UNCOV
110
                        }
×
111
                        return true, nil
2✔
112
                }
113
        }
114
        return false, nil // Dependency not in lockfile, or lockfile was empty/nil package map
1✔
115
}
116

117
func printSummaryAndNotes(
118
        c *cli.Context,
119
        depName, dependencySource string,
120
        fileDeleted, lockfileUpdated bool,
121
        lockfileLoadErr error,
122
        dependencyPath string,
123
        startTime time.Time,
124
        errWriter io.Writer,
125
) {
3✔
126
        fmt.Println("Progress: resolved 0, reused 0, downloaded 0, removed 1, done")
3✔
127
        fmt.Println()
3✔
128
        _, _ = color.New(color.FgWhite, color.Bold).Println("dependencies:")
3✔
129

3✔
130
        versionStr := "unknown"
3✔
131
        parsedInfo, parseErr := source.ParseSourceURL(dependencySource)
3✔
132
        if parseErr == nil && parsedInfo != nil && parsedInfo.Ref != "" && !strings.HasPrefix(parsedInfo.Ref, "error:") {
6✔
133
                versionStr = parsedInfo.Ref
3✔
134
        }
3✔
135

136
        _, _ = color.New(color.FgRed).Printf("- %s %s\n", depName, versionStr)
3✔
137
        fmt.Println()
3✔
138
        duration := time.Since(startTime)
3✔
139
        fmt.Printf("Done in %.1fs\n", duration.Seconds())
3✔
140

3✔
141
        if !fileDeleted {
4✔
142
                _, _ = fmt.Fprintf(errWriter, "Note: Dependency file '%s' was not deleted (either not found or error during deletion).\n", dependencyPath)
1✔
143
        }
1✔
144
        // Note: lockfileLoadErr being non-nil implies lockfile was not loaded, hence not updated.
145
        // If lockfileLoadErr is nil, but lockfileUpdated is false, it means dep was not in lockfile or save failed (which updateLockfile warns about).
146
        if lockfileLoadErr != nil {
3✔
NEW
147
                // This case is already handled by updateLockfile's warning, but we ensure the note reflects it.
×
NEW
148
                _, _ = fmt.Fprintf(errWriter, "Note: Lockfile '%s' could not be loaded to remove '%s'.\n", lockfile.LockfileName, depName)
×
149
        } else if !lockfileUpdated {
4✔
150
                _, _ = fmt.Fprintf(errWriter, "Note: Lockfile '%s' was not updated for '%s' (either dependency not found in lockfile or error during save).\n", lockfile.LockfileName, depName)
1✔
151
        }
1✔
152
}
153

154
// RemoveCmd handles the 'remove' subcommand
155
func RemoveCmd() *cli.Command {
6✔
156
        return &cli.Command{
6✔
157
                Name:      "remove",
6✔
158
                Aliases:   []string{"rm", "uninstall", "un"},
6✔
159
                Usage:     "Remove a dependency from the project",
6✔
160
                ArgsUsage: "DEPENDENCY",
6✔
161
                Action: func(c *cli.Context) error {
12✔
162
                        startTime := time.Now()
6✔
163

6✔
164
                        var errWriter io.Writer = os.Stderr
6✔
165
                        if c.App != nil && c.App.ErrWriter != nil {
12✔
166
                                errWriter = c.App.ErrWriter
6✔
167
                        }
6✔
168

169
                        if !c.Args().Present() {
6✔
NEW
170
                                return cli.Exit("Error: Dependency name argument is required.", 1)
×
NEW
171
                        }
×
172
                        depName := c.Args().First()
6✔
173

6✔
174
                        proj, depDetails, err := loadProjectConfigAndValidate(depName)
6✔
175
                        if err != nil {
9✔
176
                                return cli.Exit(fmt.Sprintf("Error: %v", err), 1)
3✔
177
                        }
3✔
178
                        dependencyPath := depDetails.Path
3✔
179
                        dependencySource := depDetails.Source
3✔
180

3✔
181
                        if err := updateManifest(proj, depName); err != nil {
3✔
NEW
182
                                return cli.Exit(fmt.Sprintf("Error: %v", err), 1)
×
UNCOV
183
                        }
×
184

185
                        fileDeleted := deleteDependencyFileAndCleanup(errWriter, dependencyPath)
3✔
186
                        lockfileUpdated, lockfileLoadErr := updateLockfile(errWriter, depName)
3✔
187

3✔
188
                        printSummaryAndNotes(c, depName, dependencySource, fileDeleted, lockfileUpdated, lockfileLoadErr, dependencyPath, startTime, errWriter)
3✔
189

3✔
190
                        return nil
3✔
191
                },
192
        }
193
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc