Files
2026-01-30 19:21:54 +01:00

220 lines
3.3 KiB
Go

package main
import (
"bufio"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
const (
configDirName = "ctx"
ignoreFileName = "ignore"
)
const defaultIgnoreText = `
.git
.svn
.hg
.gitignore
.idea
.vscode
.vs
.settings
.classpath
.project
.DS_Store
*.swp
*.swo
*.tmp
*.bak
node_modules
bower_components
jspm_packages
vendor
.venv
venv
env
.env
__pycache__
.tox
.mypy_cache
.pytest_cache
.npm
.yarn
.pnpm-store
dist
build
out
target
bin
obj
cmake-build-*
package-lock.json
yarn.lock
pnpm-lock.yaml
bun.lockb
go.sum
Cargo.lock
poetry.lock
Pipfile.lock
*.exe
*.dll
*.so
*.dylib
*.test
*.class
*.jar
*.war
*.ear
*.o
*.obj
*.sqlite
*.db
*.sqlitedb
*.zip
*.tar
*.tar.gz
*.tgz
*.rar
*.7z
*.jpg
*.jpeg
*.png
*.gif
*.ico
*.svg
*.webp
*.mp3
*.mp4
*.mov
*.avi
*.pdf
*.doc
*.docx
*.pyc
*.pyo
*.pyd
*.egg-info
*_templ.go
`
type Config struct {
ExactIgnores map[string]struct{}
ExtIgnores map[string]struct{}
ComplexGlobs []string
}
func parseConfig(text string) *Config {
cfg := &Config{
ExactIgnores: make(map[string]struct{}),
ExtIgnores: make(map[string]struct{}),
}
scanner := bufio.NewScanner(strings.NewReader(text))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if strings.HasPrefix(line, "*.") && strings.Count(line, "*") == 1 && !strings.Contains(line, "/") {
ext := strings.TrimPrefix(line, "*")
cfg.ExtIgnores[ext] = struct{}{}
} else if !strings.ContainsAny(line, "*?[]") && !strings.Contains(line, "/") {
cfg.ExactIgnores[line] = struct{}{}
} else {
cfg.ComplexGlobs = append(cfg.ComplexGlobs, line)
}
}
return cfg
}
func (c *Config) IsIgnored(name, relPath string, isDir bool) bool {
if _, ok := c.ExactIgnores[name]; ok {
return true
}
if !isDir {
ext := filepath.Ext(name)
if _, ok := c.ExtIgnores[ext]; ok {
return true
}
}
checkPath := filepath.ToSlash(relPath)
for _, p := range c.ComplexGlobs {
cleanPattern := strings.TrimSuffix(p, "/")
if matched, _ := filepath.Match(cleanPattern, name); matched {
return true
}
if matched, _ := filepath.Match(cleanPattern, checkPath); matched {
return true
}
if strings.HasPrefix(checkPath, cleanPattern+"/") {
return true
}
}
return false
}
func getConfigPath() (string, error) {
configDir, err := os.UserConfigDir()
if err != nil {
return "", err
}
appDir := filepath.Join(configDir, configDirName)
if err := os.MkdirAll(appDir, 0755); err != nil {
return "", err
}
return filepath.Join(appDir, ignoreFileName), nil
}
func loadConfig() (*Config, error) {
path, err := getConfigPath()
if err != nil {
return nil, err
}
if _, err := os.Stat(path); os.IsNotExist(err) {
if err := os.WriteFile(path, []byte(defaultIgnoreText), 0644); err != nil {
return nil, err
}
return parseConfig(defaultIgnoreText), nil
}
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return parseConfig(string(data)), nil
}
func openConfigFile() error {
path, err := getConfigPath()
if err != nil {
return err
}
if _, err := os.Stat(path); os.IsNotExist(err) {
_, _ = loadConfig()
}
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "start", "", path)
case "darwin":
cmd = exec.Command("open", path)
default:
cmd = exec.Command("xdg-open", path)
}
return cmd.Start()
}