hako/utils.go
2025-06-10 22:16:25 -03:00

140 lines
3.5 KiB
Go

package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
func runCommand(name string, args ...string) error {
cmd := exec.Command(name, args...)
fmt.Fprintf(os.Stderr, "+ %s %s\n", name, strings.Join(args, " "))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func runCommandSilent(name string, args ...string) error {
cmd := exec.Command(name, args...)
fmt.Fprintf(os.Stderr, "+ %s %s\n", name, strings.Join(args, " "))
cmd.Stdout = nil
cmd.Stderr = nil
return cmd.Run()
}
func runCommandOutput(name string, args ...string) (string, error) {
cmd := exec.Command(name, args...)
fmt.Fprintf(os.Stderr, "+ %s %s\n", name, strings.Join(args, " "))
output, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(output)), nil
}
// GitIgnore represents a .gitignore parser
type GitIgnore struct {
patterns []string
}
// NewGitIgnore creates a new GitIgnore parser by reading .gitignore file
func NewGitIgnore() (*GitIgnore, error) {
gi := &GitIgnore{}
file, err := os.Open(".gitignore")
if err != nil {
if os.IsNotExist(err) {
return gi, nil // No .gitignore file is fine
}
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" && !strings.HasPrefix(line, "#") {
gi.patterns = append(gi.patterns, line)
}
}
return gi, scanner.Err()
}
// IsIgnored checks if a file path should be ignored according to .gitignore patterns
func (gi *GitIgnore) IsIgnored(path string) bool {
for _, pattern := range gi.patterns {
if gi.matchPattern(pattern, path) {
return true
}
}
return false
}
// matchPattern implements basic gitignore pattern matching
func (gi *GitIgnore) matchPattern(pattern, path string) bool {
// Handle negation patterns (starting with !)
if strings.HasPrefix(pattern, "!") {
return false // Negation patterns would require more complex logic
}
// Convert gitignore pattern to filepath.Match compatible pattern
// Handle directory patterns (ending with /)
if strings.HasSuffix(pattern, "/") {
pattern = strings.TrimSuffix(pattern, "/")
// Check if any directory component matches
parts := strings.Split(path, string(filepath.Separator))
for _, part := range parts {
if matched, _ := filepath.Match(pattern, part); matched {
return true
}
}
return false
}
// Handle patterns with path separators
if strings.Contains(pattern, "/") {
// Exact path match
if matched, _ := filepath.Match(pattern, path); matched {
return true
}
// Check if pattern matches any suffix of the path
pathParts := strings.Split(path, string(filepath.Separator))
patternParts := strings.Split(pattern, "/")
if len(patternParts) <= len(pathParts) {
for i := 0; i <= len(pathParts)-len(patternParts); i++ {
match := true
for j, patternPart := range patternParts {
if matched, _ := filepath.Match(patternPart, pathParts[i+j]); !matched {
match = false
break
}
}
if match {
return true
}
}
}
return false
}
// Simple filename pattern - check against basename and any path component
basename := filepath.Base(path)
if matched, _ := filepath.Match(pattern, basename); matched {
return true
}
// Check if pattern matches any directory component
parts := strings.Split(path, string(filepath.Separator))
for _, part := range parts {
if matched, _ := filepath.Match(pattern, part); matched {
return true
}
}
return false
}