refactor: extract modular components from main.go

- Add internal/cli/validation.go for CLI flag validation
- Add internal/commands/hooks.go for hook management commands
- Add internal/commands/config.go for configuration commands
- Add internal/guide/guide.go for LLM usage guide
- Follows Go project layout standards for better maintainability
This commit is contained in:
carraes
2025-07-06 12:00:56 -03:00
parent 08a8da9b59
commit 7b0f7f878a
4 changed files with 567 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
package commands
import (
"fmt"
"github.com/carlosarraes/shush/internal/config"
"github.com/carlosarraes/shush/internal/types"
)
func HandleConfig(cli types.CLI) error {
switch {
case cli.Config:
return ShowConfig()
case cli.CreateConfig:
return CreateConfig()
}
return nil
}
func ShowConfig() error {
cfg, configPath, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
fmt.Println("Shush Configuration:")
if configPath != "" {
fmt.Printf("Config file: %s\n", configPath)
} else {
fmt.Println("Config file: Using defaults (no config file found)")
}
fmt.Printf("\nPreserve patterns (%d):\n", len(cfg.Preserve))
for i, pattern := range cfg.Preserve {
fmt.Printf(" %2d. %s\n", i+1, pattern)
}
fmt.Println("\nConfig file search order:")
fmt.Println(" 1. .shush.toml (current directory)")
fmt.Println(" 2. .shush.toml (git repository root)")
fmt.Println(" 3. ~/.config/.shush.toml (global)")
return nil
}
func CreateConfig() error {
if err := config.CreateExampleConfig(); err != nil {
return fmt.Errorf("failed to create config file: %w", err)
}
fmt.Println("✓ Created .shush.toml configuration file")
fmt.Println(" Edit this file to customize which comments to preserve")
fmt.Println(" Patterns support wildcards with * (e.g., '*IMPORTANT*')")
return nil
}
+248
View File
@@ -0,0 +1,248 @@
package commands
import (
"fmt"
"os"
"os/exec"
"github.com/carlosarraes/shush/internal/hooks"
"github.com/carlosarraes/shush/internal/types"
)
func HandleHooks(cli types.CLI) error {
switch {
case cli.InstallHook:
return InstallHooks(cli.HookScope)
case cli.UninstallHook:
return UninstallHooks(cli.HookScope)
case cli.ListHooks:
return ListHooks()
case cli.HookStatus:
return ShowHooksStatus()
}
return nil
}
func InstallHooks(scope string) error {
hookScope := hooks.ScopeUser
if scope == "project" {
hookScope = hooks.ScopeProject
}
userPath, _ := hooks.GetSettingsPath(hooks.ScopeUser)
projectPath, _ := hooks.GetSettingsPath(hooks.ScopeProject)
userSettings, userErr := hooks.LoadSettings(userPath)
projectSettings, projectErr := hooks.LoadSettings(projectPath)
userHasShush := userErr == nil && hooks.HasShushHook(userSettings)
projectHasShush := projectErr == nil && hooks.HasShushHook(projectSettings)
if hookScope == hooks.ScopeUser {
if userHasShush {
return fmt.Errorf("shush hook already installed for user-wide scope at %s", userPath)
}
if projectHasShush {
fmt.Printf("ℹ Project-specific hooks found at %s\n", projectPath)
fmt.Println(" User-wide hooks will take precedence and override project hooks")
}
} else {
if userHasShush {
return fmt.Errorf("user-wide hooks already installed at %s\n"+
" User-wide hooks cover all projects including this one.\n"+
" Use --uninstall-hook first if you want project-specific hooks instead", userPath)
}
if projectHasShush {
return fmt.Errorf("shush hook already installed for project scope at %s", projectPath)
}
}
path, err := hooks.GetSettingsPath(hookScope)
if err != nil {
return fmt.Errorf("failed to get settings path: %w", err)
}
settings, err := hooks.LoadSettings(path)
if err != nil {
return fmt.Errorf("failed to load settings: %w", err)
}
if err := hooks.AddShushHook(settings); err != nil {
return fmt.Errorf("failed to add shush hook: %w", err)
}
if err := hooks.SaveSettings(path, settings); err != nil {
return fmt.Errorf("failed to save settings: %w", err)
}
scopeName := "user-wide"
if hookScope == hooks.ScopeProject {
scopeName = "project"
}
fmt.Printf("✓ Hooks installed for %s scope at %s\n", scopeName, path)
fmt.Println(" Auto-cleanup will run after Claude modifies files")
return nil
}
func UninstallHooks(scope string) error {
hookScope := hooks.ScopeUser
if scope == "project" {
hookScope = hooks.ScopeProject
}
path, err := hooks.GetSettingsPath(hookScope)
if err != nil {
return fmt.Errorf("failed to get settings path: %w", err)
}
settings, err := hooks.LoadSettings(path)
if err != nil {
return fmt.Errorf("failed to load settings: %w", err)
}
scopeName := "user-wide"
if hookScope == hooks.ScopeProject {
scopeName = "project"
}
if !hooks.HasShushHook(settings) {
return fmt.Errorf("shush hook not found for %s scope at %s", scopeName, path)
}
if _, err := exec.LookPath("jq"); err == nil {
if err := uninstallWithJQ(path, scopeName); err == nil {
return nil
}
fmt.Printf("⚠️ jq removal failed, using Go implementation\n")
} else {
fmt.Printf("⚠️ jq not available. For surgical removal, install jq and run again.\n")
fmt.Printf(" Manual removal: edit %s and remove shush hook entries\n", path)
}
if err := hooks.RemoveShushHook(settings); err != nil {
return fmt.Errorf("failed to remove shush hook: %w", err)
}
if err := hooks.SaveSettings(path, settings); err != nil {
return fmt.Errorf("failed to save settings: %w", err)
}
fmt.Printf("✓ Hooks uninstalled for %s scope at %s\n", scopeName, path)
return nil
}
func uninstallWithJQ(path, scopeName string) error {
jqFilter := `
.hooks.PostToolUse |= (
map(
.hooks |= map(select(.command != "shush --changes-only"))
) |
map(select(.hooks | length > 0))
) |
if .hooks.PostToolUse | length == 0 then
.hooks |= del(.PostToolUse)
else
.
end
`
cmd := exec.Command("jq", jqFilter, path)
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("jq command failed: %w", err)
}
if err := os.WriteFile(path, output, 0644); err != nil {
return fmt.Errorf("failed to write updated settings: %w", err)
}
fmt.Printf("✓ Hooks uninstalled for %s scope at %s (using jq)\n", scopeName, path)
return nil
}
func ShowHooksStatus() error {
userPath, _ := hooks.GetSettingsPath(hooks.ScopeUser)
projectPath, _ := hooks.GetSettingsPath(hooks.ScopeProject)
userSettings, userErr := hooks.LoadSettings(userPath)
projectSettings, projectErr := hooks.LoadSettings(projectPath)
userHasShush := userErr == nil && hooks.HasShushHook(userSettings)
projectHasShush := projectErr == nil && hooks.HasShushHook(projectSettings)
fmt.Println("Shush Hooks Status:")
fmt.Printf("User-wide (%s): %s\n", userPath, getHookStatus(userSettings, userErr))
fmt.Printf("Project (%s): %s\n", projectPath, getHookStatus(projectSettings, projectErr))
if userHasShush && projectHasShush {
fmt.Println("\n⚠️ Warning: Both user-wide and project hooks are installed")
fmt.Println(" This will cause shush to run twice on every file modification")
fmt.Println(" Consider removing project hooks: shush --uninstall-hook -s project")
} else if userHasShush {
fmt.Println("\n✓ User-wide hooks will handle all projects including this one")
} else if projectHasShush {
fmt.Println("\n✓ Project-specific hooks active for this project only")
}
return nil
}
func getHookStatus(settings *hooks.ClaudeSettings, err error) string {
if err != nil {
return "Not configured"
}
if hooks.HasShushHook(settings) {
return "✓ Installed"
}
return "Not installed"
}
func ListHooks() error {
userPath, _ := hooks.GetSettingsPath(hooks.ScopeUser)
projectPath, _ := hooks.GetSettingsPath(hooks.ScopeProject)
fmt.Println("Claude Code Hooks Configuration:")
fmt.Println("User-wide:")
if err := listHooksForPath(userPath); err != nil {
fmt.Println(" No hooks configured")
}
fmt.Println("Project:")
if err := listHooksForPath(projectPath); err != nil {
fmt.Println(" No hooks configured")
}
return nil
}
func listHooksForPath(path string) error {
settings, err := hooks.LoadSettings(path)
if err != nil {
return err
}
if len(settings.Hooks) == 0 {
return fmt.Errorf("no hooks")
}
hasShush := hooks.HasShushHook(settings)
for event, configs := range settings.Hooks {
for _, config := range configs {
for _, hook := range config.Hooks {
if hook.Command == "shush --changes-only" {
fmt.Printf(" ✓ shush --changes-only (%s: %s)\n", event, config.Matcher)
} else {
fmt.Printf(" ✓ %s (%s: %s)\n", hook.Command, event, config.Matcher)
}
}
}
}
if !hasShush {
fmt.Println(" (No shush hooks found)")
}
return nil
}