Skip to content

Commit aa7430c

Browse files
committed
✨ Implement command management system with add, delete, edit, find, list, and run functionalities
1 parent b5813cc commit aa7430c

File tree

10 files changed

+692
-2
lines changed

10 files changed

+692
-2
lines changed

Makefile

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
.PHONY: build test clean install help
2+
3+
# Variables
4+
BINARY_NAME=zut
5+
INSTALL_PATH=/usr/local/bin
6+
7+
# Default target
8+
all: build
9+
10+
# Build the application
11+
build:
12+
@echo "Building $(BINARY_NAME)..."
13+
@go build -o $(BINARY_NAME) .
14+
@echo "Build complete: ./$(BINARY_NAME)"
15+
16+
# Run tests
17+
test:
18+
@echo "Running tests..."
19+
@go test ./... -v
20+
21+
# Run tests with coverage
22+
test-coverage:
23+
@echo "Running tests with coverage..."
24+
@go test ./... -cover
25+
26+
# Clean build artifacts
27+
clean:
28+
@echo "Cleaning..."
29+
@rm -f $(BINARY_NAME)
30+
@rm -rf ~/.config/zut/commands.json
31+
@echo "Clean complete"
32+
33+
# Install binary to system
34+
install: build
35+
@echo "Installing $(BINARY_NAME) to $(INSTALL_PATH)..."
36+
@sudo mv $(BINARY_NAME) $(INSTALL_PATH)/
37+
@echo "Installation complete. Run '$(BINARY_NAME)' from anywhere!"
38+
39+
# Uninstall binary from system
40+
uninstall:
41+
@echo "Uninstalling $(BINARY_NAME)..."
42+
@sudo rm -f $(INSTALL_PATH)/$(BINARY_NAME)
43+
@echo "Uninstall complete"
44+
45+
# Run the application
46+
run: build
47+
@./$(BINARY_NAME)
48+
49+
# Format code
50+
fmt:
51+
@echo "Formatting code..."
52+
@go fmt ./...
53+
54+
# Run linter
55+
lint:
56+
@echo "Running linter..."
57+
@golangci-lint run
58+
59+
# Build for multiple platforms
60+
build-all:
61+
@echo "Building for multiple platforms..."
62+
@GOOS=linux GOARCH=amd64 go build -o $(BINARY_NAME)-linux-amd64 .
63+
@GOOS=darwin GOARCH=amd64 go build -o $(BINARY_NAME)-darwin-amd64 .
64+
@GOOS=darwin GOARCH=arm64 go build -o $(BINARY_NAME)-darwin-arm64 .
65+
@GOOS=windows GOARCH=amd64 go build -o $(BINARY_NAME)-windows-amd64.exe .
66+
@echo "Cross-platform build complete"
67+
68+
# Show help
69+
help:
70+
@echo "Available targets:"
71+
@echo " build - Build the application"
72+
@echo " test - Run tests"
73+
@echo " test-coverage - Run tests with coverage"
74+
@echo " clean - Remove build artifacts"
75+
@echo " install - Install to $(INSTALL_PATH)"
76+
@echo " uninstall - Remove from $(INSTALL_PATH)"
77+
@echo " run - Build and run the application"
78+
@echo " fmt - Format code"
79+
@echo " lint - Run linter"
80+
@echo " build-all - Build for multiple platforms"
81+
@echo " help - Show this help message"

cmd/add.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package command
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/geekloper/zut/internal/core"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
// NewAddCommand creates the add command
11+
func NewAddCommand() *cobra.Command {
12+
var alias, command, tag string
13+
14+
cmd := &cobra.Command{
15+
Use: "add",
16+
Short: "Add a new command",
17+
Long: "Store a new command with a unique alias and optional tag/description",
18+
Example: ` zut add -a "git-push-f" -c "git push --force-with-lease origin main" -t "Dangerous but quick push"
19+
zut add --alias "docker-clean" --command "docker system prune -af" --tag "Clean up Docker"`,
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
// Validate required flags
22+
if alias == "" {
23+
return fmt.Errorf("alias is required (use -a or --alias)")
24+
}
25+
if command == "" {
26+
return fmt.Errorf("command is required (use -c or --command)")
27+
}
28+
29+
// Create manager
30+
manager, err := core.NewManager()
31+
if err != nil {
32+
return fmt.Errorf("failed to initialize: %w", err)
33+
}
34+
35+
// Add command
36+
if err := manager.AddCommand(alias, command, tag); err != nil {
37+
return err
38+
}
39+
40+
fmt.Printf("✓ Command '%s' added successfully\n", alias)
41+
return nil
42+
},
43+
}
44+
45+
// Add flags
46+
cmd.Flags().StringVarP(&alias, "alias", "a", "", "Unique alias for the command (required)")
47+
cmd.Flags().StringVarP(&command, "command", "c", "", "The command to save (required)")
48+
cmd.Flags().StringVarP(&tag, "tag", "t", "", "Optional tag/description")
49+
50+
return cmd
51+
}

cmd/delete.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package command
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/geekloper/zut/internal/core"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
// NewDeleteCommand creates the delete command
11+
func NewDeleteCommand() *cobra.Command {
12+
cmd := &cobra.Command{
13+
Use: "delete [alias]",
14+
Short: "Delete a saved command",
15+
Long: "Remove a saved command by its alias",
16+
Example: ` zut delete git-push-f
17+
zut delete docker-clean`,
18+
Args: cobra.ExactArgs(1),
19+
RunE: func(cmd *cobra.Command, args []string) error {
20+
alias := args[0]
21+
22+
// Create manager
23+
manager, err := core.NewManager()
24+
if err != nil {
25+
return fmt.Errorf("failed to initialize: %w", err)
26+
}
27+
28+
// Delete command
29+
if err := manager.DeleteCommand(alias); err != nil {
30+
return err
31+
}
32+
33+
fmt.Printf("✓ Command '%s' deleted successfully\n", alias)
34+
return nil
35+
},
36+
}
37+
38+
return cmd
39+
}

cmd/edit.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package command
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/geekloper/zut/internal/core"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
// NewEditCommand creates the edit command
11+
func NewEditCommand() *cobra.Command {
12+
var newCommand, newTag string
13+
14+
cmd := &cobra.Command{
15+
Use: "edit [alias]",
16+
Short: "Edit an existing command",
17+
Long: "Modify the command string or tag of an existing saved command",
18+
Example: ` zut edit git-push-f -c "git push --force"
19+
zut edit docker-clean -t "Clean Docker images and containers"
20+
zut edit myalias -c "new command" -t "new tag"`,
21+
Args: cobra.ExactArgs(1),
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
alias := args[0]
24+
25+
// Validate at least one flag is provided
26+
if newCommand == "" && newTag == "" {
27+
return fmt.Errorf("at least one of --command or --tag must be provided")
28+
}
29+
30+
// Create manager
31+
manager, err := core.NewManager()
32+
if err != nil {
33+
return fmt.Errorf("failed to initialize: %w", err)
34+
}
35+
36+
// Edit command
37+
if err := manager.EditCommand(alias, newCommand, newTag); err != nil {
38+
return err
39+
}
40+
41+
fmt.Printf("✓ Command '%s' updated successfully\n", alias)
42+
return nil
43+
},
44+
}
45+
46+
// Add flags
47+
cmd.Flags().StringVarP(&newCommand, "command", "c", "", "New command string")
48+
cmd.Flags().StringVarP(&newTag, "tag", "t", "", "New tag/description")
49+
50+
return cmd
51+
}

cmd/find.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package command
2+
3+
import (
4+
"fmt"
5+
"sort"
6+
"strings"
7+
8+
"github.com/geekloper/zut/internal/core"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
// NewFindCommand creates the find command (alias for list --search)
13+
func NewFindCommand() *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "find [search-term]",
16+
Short: "Search for commands",
17+
Long: "Search for saved commands by alias, tag, or command content",
18+
Example: ` zut find git
19+
zut find "docker clean"
20+
zut find push`,
21+
Args: cobra.ExactArgs(1),
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
searchTerm := args[0]
24+
25+
// Create manager
26+
manager, err := core.NewManager()
27+
if err != nil {
28+
return fmt.Errorf("failed to initialize: %w", err)
29+
}
30+
31+
// Search commands
32+
entries := manager.SearchCommands(searchTerm)
33+
34+
// Check if empty
35+
if len(entries) == 0 {
36+
fmt.Printf("No commands found matching '%s'\n", searchTerm)
37+
return nil
38+
}
39+
40+
// Sort by alias for consistent output
41+
sort.Slice(entries, func(i, j int) bool {
42+
return entries[i].Alias < entries[j].Alias
43+
})
44+
45+
// Print header
46+
fmt.Println()
47+
fmt.Printf("Search results for '%s':\n", searchTerm)
48+
fmt.Println(strings.Repeat("─", 100))
49+
printTableRow("ALIAS", "COMMAND", "TAG")
50+
fmt.Println(strings.Repeat("─", 100))
51+
52+
// Print entries
53+
for _, entry := range entries {
54+
printTableRow(entry.Alias, truncate(entry.Command, 50), entry.Tag)
55+
}
56+
fmt.Println()
57+
fmt.Printf("Found: %d command(s)\n", len(entries))
58+
59+
return nil
60+
},
61+
}
62+
63+
return cmd
64+
}

cmd/list.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package command
2+
3+
import (
4+
"fmt"
5+
"sort"
6+
"strings"
7+
8+
"github.com/geekloper/zut/internal/core"
9+
"github.com/geekloper/zut/pkg/storage"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
// NewListCommand creates the list command
14+
func NewListCommand() *cobra.Command {
15+
var search string
16+
17+
cmd := &cobra.Command{
18+
Use: "list",
19+
Short: "List all saved commands",
20+
Long: "Display all saved commands or search/filter by alias, tag, or command content",
21+
Example: ` zut list
22+
zut list -s "git"
23+
zut list --search "docker"`,
24+
RunE: func(cmd *cobra.Command, args []string) error {
25+
// Create manager
26+
manager, err := core.NewManager()
27+
if err != nil {
28+
return fmt.Errorf("failed to initialize: %w", err)
29+
}
30+
31+
// Get commands (filtered or all)
32+
var entries []storage.CommandEntry
33+
if search != "" {
34+
entries = manager.SearchCommands(search)
35+
} else {
36+
entries = manager.ListCommands()
37+
}
38+
39+
// Check if empty
40+
if len(entries) == 0 {
41+
if search != "" {
42+
fmt.Println("No commands found matching the search term")
43+
} else {
44+
fmt.Println("No commands saved yet. Use 'zut add' to add a command.")
45+
}
46+
return nil
47+
}
48+
49+
// Sort by alias for consistent output
50+
sort.Slice(entries, func(i, j int) bool {
51+
return entries[i].Alias < entries[j].Alias
52+
})
53+
54+
// Print header
55+
fmt.Println()
56+
printTableRow("ALIAS", "COMMAND", "TAG")
57+
fmt.Println(strings.Repeat("─", 100))
58+
59+
// Print entries
60+
for _, entry := range entries {
61+
printTableRow(entry.Alias, truncate(entry.Command, 50), entry.Tag)
62+
}
63+
fmt.Println()
64+
fmt.Printf("Total: %d command(s)\n", len(entries))
65+
66+
return nil
67+
},
68+
}
69+
70+
// Add flags
71+
cmd.Flags().StringVarP(&search, "search", "s", "", "Search term to filter commands")
72+
73+
return cmd
74+
}
75+
76+
// printTableRow prints a formatted table row
77+
func printTableRow(alias, command, tag string) {
78+
fmt.Printf("%-20s %-52s %-25s\n", truncate(alias, 20), truncate(command, 50), truncate(tag, 25))
79+
}
80+
81+
// truncate truncates a string to the specified length
82+
func truncate(s string, maxLen int) string {
83+
if len(s) <= maxLen {
84+
return s
85+
}
86+
if maxLen <= 3 {
87+
return s[:maxLen]
88+
}
89+
return s[:maxLen-3] + "..."
90+
}

0 commit comments

Comments
 (0)