update sigil
This commit is contained in:
@@ -30,7 +30,12 @@ go run . <command>
|
|||||||
## `config.lua`
|
## `config.lua`
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
return {
|
---@class SigilConfig
|
||||||
|
---@field target table<string, string|boolean>
|
||||||
|
---@field ignore? string[]
|
||||||
|
|
||||||
|
---@type SigilConfig
|
||||||
|
local config = {
|
||||||
target = {
|
target = {
|
||||||
linux = "~/.config/nvim",
|
linux = "~/.config/nvim",
|
||||||
macos = "~/Library/Application Support/nvim",
|
macos = "~/Library/Application Support/nvim",
|
||||||
@@ -38,7 +43,14 @@ return {
|
|||||||
windows = false,
|
windows = false,
|
||||||
default = "~/.config/nvim",
|
default = "~/.config/nvim",
|
||||||
},
|
},
|
||||||
|
ignore = {
|
||||||
|
"**/.DS_Store",
|
||||||
|
"**/*.tmp",
|
||||||
|
"cache/**",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
```
|
```
|
||||||
|
|
||||||
## Spec formats
|
## Spec formats
|
||||||
@@ -53,4 +65,5 @@ return {
|
|||||||
|
|
||||||
- Uses `SIGIL_REPO` env var to override the repo path.
|
- Uses `SIGIL_REPO` env var to override the repo path.
|
||||||
- Conflicts are detected (existing non-symlink files will stop apply).
|
- Conflicts are detected (existing non-symlink files will stop apply).
|
||||||
|
- `config.ignore` supports gitignore-like globs (`*`, `?`, `**`) relative to each package `files/` directory.
|
||||||
- Prefer `sigil add` over manual edits in `files/`.
|
- Prefer `sigil add` over manual edits in `files/`.
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -22,6 +24,8 @@ const (
|
|||||||
type packageConfig struct {
|
type packageConfig struct {
|
||||||
targets map[string]string
|
targets map[string]string
|
||||||
disabled map[string]bool
|
disabled map[string]bool
|
||||||
|
ignore []string
|
||||||
|
compiledIgnores []*regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
var errTargetDisabled = errors.New("target disabled for this platform")
|
var errTargetDisabled = errors.New("target disabled for this platform")
|
||||||
@@ -262,7 +266,7 @@ func applyCmd(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := applyPackage(filesDir, targetRoot); err != nil {
|
if err := applyPackage(filesDir, targetRoot, cfg); err != nil {
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,7 +414,12 @@ func addCmd(args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return applyPackage(filesDir, targetRoot)
|
cfg, err := loadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyPackage(filesDir, targetRoot, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unlinkCmd(args []string) error {
|
func unlinkCmd(args []string) error {
|
||||||
@@ -535,7 +544,7 @@ func removeCmd(args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyPackage(filesDir, targetRoot string) error {
|
func applyPackage(filesDir, targetRoot string, cfg *packageConfig) error {
|
||||||
if err := ensureDir(targetRoot); err != nil {
|
if err := ensureDir(targetRoot); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -552,6 +561,14 @@ func applyPackage(filesDir, targetRoot string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldIgnorePath(rel, cfg) {
|
||||||
|
if entry.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
targetPath := filepath.Join(targetRoot, rel)
|
targetPath := filepath.Join(targetRoot, rel)
|
||||||
|
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
@@ -653,7 +670,16 @@ func loadConfig(path string) (*packageConfig, error) {
|
|||||||
return nil, errors.New("config.target is empty")
|
return nil, errors.New("config.target is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &packageConfig{targets: targets, disabled: disabled}, nil
|
ignore, err := parseIgnore(tbl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
compiledIgnores, err := compileIgnorePatterns(ignore)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &packageConfig{targets: targets, disabled: disabled, ignore: ignore, compiledIgnores: compiledIgnores}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func statusCmd() error {
|
func statusCmd() error {
|
||||||
@@ -723,6 +749,108 @@ func selectTarget(cfg *packageConfig) (string, error) {
|
|||||||
return "", fmt.Errorf("missing target for %s and default", osKey)
|
return "", fmt.Errorf("missing target for %s and default", osKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseIgnore(cfgTbl *lua.LTable) ([]string, error) {
|
||||||
|
ignoreVal := cfgTbl.RawGetString("ignore")
|
||||||
|
if ignoreVal == lua.LNil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreTbl, ok := ignoreVal.(*lua.LTable)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("config.ignore must be an array of strings")
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore := make([]string, 0, ignoreTbl.Len())
|
||||||
|
var parseErr error
|
||||||
|
ignoreTbl.ForEach(func(k, v lua.LValue) {
|
||||||
|
if parseErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := k.(lua.LNumber); !ok {
|
||||||
|
parseErr = errors.New("config.ignore must be an array of strings")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := v.(lua.LString)
|
||||||
|
if !ok {
|
||||||
|
parseErr = errors.New("config.ignore must contain only strings")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern := strings.TrimSpace(string(s))
|
||||||
|
if pattern == "" {
|
||||||
|
parseErr = errors.New("config.ignore cannot contain empty patterns")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore = append(ignore, pattern)
|
||||||
|
})
|
||||||
|
if parseErr != nil {
|
||||||
|
return nil, parseErr
|
||||||
|
}
|
||||||
|
return ignore, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileIgnorePatterns(patterns []string) ([]*regexp.Regexp, error) {
|
||||||
|
compiled := make([]*regexp.Regexp, 0, len(patterns))
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
re, err := globToRegexp(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid ignore pattern %q: %w", pattern, err)
|
||||||
|
}
|
||||||
|
compiled = append(compiled, re)
|
||||||
|
}
|
||||||
|
return compiled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func globToRegexp(pattern string) (*regexp.Regexp, error) {
|
||||||
|
pattern = strings.ReplaceAll(pattern, "\\", "/")
|
||||||
|
pattern = strings.TrimPrefix(pattern, "./")
|
||||||
|
if strings.HasPrefix(pattern, "/") {
|
||||||
|
pattern = strings.TrimPrefix(pattern, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("^")
|
||||||
|
for i := 0; i < len(pattern); {
|
||||||
|
if i+1 < len(pattern) && pattern[i] == '*' && pattern[i+1] == '*' {
|
||||||
|
b.WriteString(".*")
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := pattern[i]
|
||||||
|
switch ch {
|
||||||
|
case '*':
|
||||||
|
b.WriteString("[^/]*")
|
||||||
|
case '?':
|
||||||
|
b.WriteString("[^/]")
|
||||||
|
default:
|
||||||
|
b.WriteString(regexp.QuoteMeta(string(ch)))
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
b.WriteString("$")
|
||||||
|
|
||||||
|
return regexp.Compile(b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldIgnorePath(rel string, cfg *packageConfig) bool {
|
||||||
|
if cfg == nil || len(cfg.compiledIgnores) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := path.Clean(filepath.ToSlash(rel))
|
||||||
|
for _, re := range cfg.compiledIgnores {
|
||||||
|
if re.MatchString(normalized) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func repoPath() (string, error) {
|
func repoPath() (string, error) {
|
||||||
if override := os.Getenv("SIGIL_REPO"); override != "" {
|
if override := os.Getenv("SIGIL_REPO"); override != "" {
|
||||||
return filepath.Abs(expandHome(override))
|
return filepath.Abs(expandHome(override))
|
||||||
@@ -772,7 +900,7 @@ func writeConfig(path, targetRoot string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prettyTarget := compressHome(targetRoot)
|
prettyTarget := compressHome(targetRoot)
|
||||||
content := fmt.Sprintf("return {\n\ttarget = {\n\t\t%s = %q,\n\t\tdefault = %q,\n\t},\n}\n", osKey, prettyTarget, prettyTarget)
|
content := fmt.Sprintf("---@class SigilConfig\n---@field target table<string, string|boolean>\n---@field ignore? string[]\n\n---@type SigilConfig\nlocal config = {\n\ttarget = {\n\t\t%s = %q,\n\t\tdefault = %q,\n\t},\n\tignore = {\n\t\t-- \"**/.DS_Store\",\n\t\t-- \"**/*.tmp\",\n\t\t-- \"cache/**\",\n\t},\n}\n\nreturn config\n", osKey, prettyTarget, prettyTarget)
|
||||||
return os.WriteFile(path, []byte(content), 0o644)
|
return os.WriteFile(path, []byte(content), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user