move core logic to internal/core package, bump to 0.1.3
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configFileName = "config.lua"
|
ConfigFileName = "config.lua"
|
||||||
filesDirName = "files"
|
FilesDirName = "files"
|
||||||
)
|
)
|
||||||
|
|
||||||
type packageConfig struct {
|
type packageConfig struct {
|
||||||
@@ -25,9 +25,9 @@ type packageConfig struct {
|
|||||||
compiledIgnores []*regexp.Regexp
|
compiledIgnores []*regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
var errTargetDisabled = errors.New("target disabled for this platform")
|
var ErrTargetDisabled = errors.New("target disabled for this platform")
|
||||||
|
|
||||||
func loadConfig(path string) (*packageConfig, error) {
|
func LoadConfig(path string) (*packageConfig, error) {
|
||||||
L := lua.NewState()
|
L := lua.NewState()
|
||||||
defer L.Close()
|
defer L.Close()
|
||||||
L.OpenLibs()
|
L.OpenLibs()
|
||||||
@@ -69,18 +69,18 @@ func loadConfig(path string) (*packageConfig, error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targets[string(ks)] = expandHome(string(vs))
|
targets[string(ks)] = ExpandHome(string(vs))
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(targets) == 0 && len(disabled) == 0 {
|
if len(targets) == 0 && len(disabled) == 0 {
|
||||||
return nil, errors.New("config.target is empty")
|
return nil, errors.New("config.target is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
ignore, err := parseIgnore(tbl)
|
ignore, err := ParseIgnore(tbl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
compiledIgnores, err := compileIgnorePatterns(ignore)
|
compiledIgnores, err := CompileIgnorePatterns(ignore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -88,24 +88,24 @@ func loadConfig(path string) (*packageConfig, error) {
|
|||||||
return &packageConfig{targets: targets, disabled: disabled, ignore: ignore, compiledIgnores: compiledIgnores}, nil
|
return &packageConfig{targets: targets, disabled: disabled, ignore: ignore, compiledIgnores: compiledIgnores}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectTarget(cfg *packageConfig) (string, error) {
|
func SelectTarget(cfg *packageConfig) (string, error) {
|
||||||
osKey := runtime.GOOS
|
osKey := runtime.GOOS
|
||||||
if osKey == "darwin" {
|
if osKey == "darwin" {
|
||||||
osKey = "macos"
|
osKey = "macos"
|
||||||
}
|
}
|
||||||
if cfg.disabled[osKey] {
|
if cfg.disabled[osKey] {
|
||||||
return "", errTargetDisabled
|
return "", ErrTargetDisabled
|
||||||
}
|
}
|
||||||
if target, ok := cfg.targets[osKey]; ok {
|
if target, ok := cfg.targets[osKey]; ok {
|
||||||
return expandHome(target), nil
|
return ExpandHome(target), nil
|
||||||
}
|
}
|
||||||
if target, ok := cfg.targets["default"]; ok {
|
if target, ok := cfg.targets["default"]; ok {
|
||||||
return expandHome(target), nil
|
return ExpandHome(target), nil
|
||||||
}
|
}
|
||||||
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) {
|
func ParseIgnore(cfgTbl *lua.LTable) ([]string, error) {
|
||||||
ignoreVal := cfgTbl.RawGetString("ignore")
|
ignoreVal := cfgTbl.RawGetString("ignore")
|
||||||
if ignoreVal == lua.LNil {
|
if ignoreVal == lua.LNil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -148,10 +148,10 @@ func parseIgnore(cfgTbl *lua.LTable) ([]string, error) {
|
|||||||
return ignore, nil
|
return ignore, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileIgnorePatterns(patterns []string) ([]*regexp.Regexp, error) {
|
func CompileIgnorePatterns(patterns []string) ([]*regexp.Regexp, error) {
|
||||||
compiled := make([]*regexp.Regexp, 0, len(patterns))
|
compiled := make([]*regexp.Regexp, 0, len(patterns))
|
||||||
for _, pattern := range patterns {
|
for _, pattern := range patterns {
|
||||||
re, err := globToRegexp(pattern)
|
re, err := GlobToRegexp(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid ignore pattern %q: %w", pattern, err)
|
return nil, fmt.Errorf("invalid ignore pattern %q: %w", pattern, err)
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ func compileIgnorePatterns(patterns []string) ([]*regexp.Regexp, error) {
|
|||||||
return compiled, nil
|
return compiled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func globToRegexp(pattern string) (*regexp.Regexp, error) {
|
func GlobToRegexp(pattern string) (*regexp.Regexp, error) {
|
||||||
pattern = strings.ReplaceAll(pattern, "\\", "/")
|
pattern = strings.ReplaceAll(pattern, "\\", "/")
|
||||||
pattern = strings.TrimPrefix(pattern, "./")
|
pattern = strings.TrimPrefix(pattern, "./")
|
||||||
if strings.HasPrefix(pattern, "/") {
|
if strings.HasPrefix(pattern, "/") {
|
||||||
@@ -192,7 +192,7 @@ func globToRegexp(pattern string) (*regexp.Regexp, error) {
|
|||||||
return regexp.Compile(b.String())
|
return regexp.Compile(b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldIgnorePath(rel string, cfg *packageConfig) bool {
|
func ShouldIgnorePath(rel string, cfg *packageConfig) bool {
|
||||||
if cfg == nil || len(cfg.compiledIgnores) == 0 {
|
if cfg == nil || len(cfg.compiledIgnores) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -207,13 +207,13 @@ func shouldIgnorePath(rel string, cfg *packageConfig) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeConfig(path, targetRoot string) error {
|
func WriteConfig(path, targetRoot string) error {
|
||||||
osKey := "linux"
|
osKey := "linux"
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
osKey = "macos"
|
osKey = "macos"
|
||||||
}
|
}
|
||||||
|
|
||||||
prettyTarget := compressHome(targetRoot)
|
prettyTarget := CompressHome(targetRoot)
|
||||||
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)
|
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)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -50,9 +50,9 @@ func TestGlobToRegexp(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.pattern, func(t *testing.T) {
|
t.Run(tt.pattern, func(t *testing.T) {
|
||||||
re, err := globToRegexp(tt.pattern)
|
re, err := GlobToRegexp(tt.pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("globToRegexp(%q) error: %v", tt.pattern, err)
|
t.Fatalf("GlobToRegexp(%q) error: %v", tt.pattern, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, m := range tt.matches {
|
for _, m := range tt.matches {
|
||||||
@@ -76,20 +76,20 @@ func TestShouldIgnorePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test with nil config
|
// Test with nil config
|
||||||
if shouldIgnorePath("foo.txt", nil) {
|
if ShouldIgnorePath("foo.txt", nil) {
|
||||||
t.Error("shouldIgnorePath with nil config should return false")
|
t.Error("ShouldIgnorePath with nil config should return false")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with empty ignores
|
// Test with empty ignores
|
||||||
if shouldIgnorePath("foo.txt", cfg) {
|
if ShouldIgnorePath("foo.txt", cfg) {
|
||||||
t.Error("shouldIgnorePath with empty ignores should return false")
|
t.Error("ShouldIgnorePath with empty ignores should return false")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add some patterns and re-test
|
// Add some patterns and re-test
|
||||||
patterns := []string{"*.log", "cache/**", "**/.DS_Store"}
|
patterns := []string{"*.log", "cache/**", "**/.DS_Store"}
|
||||||
compiled, err := compileIgnorePatterns(patterns)
|
compiled, err := CompileIgnorePatterns(patterns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("compileIgnorePatterns error: %v", err)
|
t.Fatalf("CompileIgnorePatterns error: %v", err)
|
||||||
}
|
}
|
||||||
cfg.compiledIgnores = compiled
|
cfg.compiledIgnores = compiled
|
||||||
|
|
||||||
@@ -110,9 +110,9 @@ func TestShouldIgnorePath(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.path, func(t *testing.T) {
|
t.Run(tt.path, func(t *testing.T) {
|
||||||
got := shouldIgnorePath(tt.path, cfg)
|
got := ShouldIgnorePath(tt.path, cfg)
|
||||||
if got != tt.ignored {
|
if got != tt.ignored {
|
||||||
t.Errorf("shouldIgnorePath(%q) = %v, want %v", tt.path, got, tt.ignored)
|
t.Errorf("ShouldIgnorePath(%q) = %v, want %v", tt.path, got, tt.ignored)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyPackage(filesDir, targetRoot string, cfg *packageConfig) 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ func applyPackage(filesDir, targetRoot string, cfg *packageConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldIgnorePath(rel, cfg) {
|
if ShouldIgnorePath(rel, cfg) {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
@@ -38,18 +38,18 @@ func applyPackage(filesDir, targetRoot string, cfg *packageConfig) error {
|
|||||||
targetPath := filepath.Join(targetRoot, rel)
|
targetPath := filepath.Join(targetRoot, rel)
|
||||||
|
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
return ensureDir(targetPath)
|
return EnsureDir(targetPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
srcAbs, err := filepath.Abs(path)
|
srcAbs, err := filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return linkFile(srcAbs, targetPath)
|
return LinkFile(srcAbs, targetPath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func linkFile(src, dst string) error {
|
func LinkFile(src, dst string) error {
|
||||||
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ func linkFile(src, dst string) error {
|
|||||||
return os.Symlink(src, dst)
|
return os.Symlink(src, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureDir(path string) error {
|
func EnsureDir(path string) error {
|
||||||
info, err := os.Lstat(path)
|
info, err := os.Lstat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
@@ -87,7 +87,7 @@ func ensureDir(path string) error {
|
|||||||
return os.MkdirAll(path, 0o755)
|
return os.MkdirAll(path, 0o755)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findStaleLinks(filesDir, targetRoot string) ([]string, error) {
|
func FindStaleLinks(filesDir, targetRoot string) ([]string, error) {
|
||||||
filesAbs, err := filepath.Abs(filesDir)
|
filesAbs, err := filepath.Abs(filesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -155,7 +155,7 @@ func findStaleLinks(filesDir, targetRoot string) ([]string, error) {
|
|||||||
return stale, nil
|
return stale, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeLinks(paths []string, dryRun bool) error {
|
func RemoveLinks(paths []string, dryRun bool) error {
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
if dryRun {
|
if dryRun {
|
||||||
fmt.Printf("dry-run: remove %s\n", path)
|
fmt.Printf("dry-run: remove %s\n", path)
|
||||||
@@ -169,12 +169,12 @@ func removeLinks(paths []string, dryRun bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleStaleLinks(stales []string) error {
|
func HandleStaleLinks(stales []string) error {
|
||||||
if len(stales) == 0 {
|
if len(stales) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := repoPath()
|
repo, err := RepoPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ func handleStaleLinks(stales []string) error {
|
|||||||
for _, path := range stales {
|
for _, path := range stales {
|
||||||
fmt.Printf("stale: %s\n", path)
|
fmt.Printf("stale: %s\n", path)
|
||||||
|
|
||||||
canUnlink, err := staleHasRepoFile(path, repo)
|
canUnlink, err := StaleHasRepoFile(path, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -210,7 +210,7 @@ func handleStaleLinks(stales []string) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if choice == "u" && canUnlink {
|
if choice == "u" && canUnlink {
|
||||||
if err := unlinkStale(path, repo); err != nil {
|
if err := UnlinkStale(path, repo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -221,15 +221,15 @@ func handleStaleLinks(stales []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func staleHasRepoFile(targetPath, repo string) (bool, error) {
|
func StaleHasRepoFile(targetPath, repo string) (bool, error) {
|
||||||
repoPath, err := repoPathForTarget(targetPath, repo)
|
RepoPath, err := RepoPathForTarget(targetPath, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if repoPath == "" {
|
if RepoPath == "" {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(repoPath); errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(RepoPath); errors.Is(err, os.ErrNotExist) {
|
||||||
return false, nil
|
return false, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -237,12 +237,12 @@ func staleHasRepoFile(targetPath, repo string) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func unlinkStale(targetPath, repo string) error {
|
func UnlinkStale(targetPath, repo string) error {
|
||||||
repoPath, err := repoPathForTarget(targetPath, repo)
|
RepoPath, err := RepoPathForTarget(targetPath, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if repoPath == "" {
|
if RepoPath == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,19 +250,19 @@ func unlinkStale(targetPath, repo string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := copyFile(repoPath, targetPath); err != nil {
|
if err := CopyFile(RepoPath, targetPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Remove(repoPath); err != nil {
|
if err := os.Remove(RepoPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("unlinked %s (removed %s)\n", targetPath, repoPath)
|
fmt.Printf("unlinked %s (removed %s)\n", targetPath, RepoPath)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func restorePackage(filesDir, targetRoot string, dryRun bool) error {
|
func RestorePackage(filesDir, targetRoot string, dryRun bool) error {
|
||||||
filesAbs, err := filepath.Abs(filesDir)
|
filesAbs, err := filepath.Abs(filesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -284,11 +284,11 @@ func restorePackage(filesDir, targetRoot string, dryRun bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
targetPath := filepath.Join(targetRoot, rel)
|
targetPath := filepath.Join(targetRoot, rel)
|
||||||
return restoreOne(path, targetPath, filesAbs, dryRun)
|
return RestoreOne(path, targetPath, filesAbs, dryRun)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func restorePath(filesDir, targetRoot, relPath string, dryRun bool) error {
|
func RestorePath(filesDir, targetRoot, relPath string, dryRun bool) error {
|
||||||
filesAbs, err := filepath.Abs(filesDir)
|
filesAbs, err := filepath.Abs(filesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -318,7 +318,7 @@ func restorePath(filesDir, targetRoot, relPath string, dryRun bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
targetPath := filepath.Join(targetRoot, rel)
|
targetPath := filepath.Join(targetRoot, rel)
|
||||||
return restoreOne(path, targetPath, filesAbs, dryRun)
|
return RestoreOne(path, targetPath, filesAbs, dryRun)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,10 +326,10 @@ func restorePath(filesDir, targetRoot, relPath string, dryRun bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return restoreOne(sourcePath, filepath.Join(targetRoot, rel), filesAbs, dryRun)
|
return RestoreOne(sourcePath, filepath.Join(targetRoot, rel), filesAbs, dryRun)
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreOne(sourcePath, targetPath, filesAbs string, dryRun bool) error {
|
func RestoreOne(sourcePath, targetPath, filesAbs string, dryRun bool) error {
|
||||||
info, err := os.Lstat(targetPath)
|
info, err := os.Lstat(targetPath)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
return nil
|
return nil
|
||||||
@@ -364,10 +364,10 @@ func restoreOne(sourcePath, targetPath, filesAbs string, dryRun bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("restored %s\n", targetPath)
|
fmt.Printf("restored %s\n", targetPath)
|
||||||
return copyFile(sourcePath, targetPath)
|
return CopyFile(sourcePath, targetPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFile(src, dst string) error {
|
func CopyFile(src, dst string) error {
|
||||||
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -396,7 +396,7 @@ func copyFile(src, dst string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveDirContents(srcDir, destDir string) error {
|
func MoveDirContents(srcDir, destDir string) error {
|
||||||
entries, err := os.ReadDir(srcDir)
|
entries, err := os.ReadDir(srcDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -419,3 +419,65 @@ func moveDirContents(srcDir, destDir string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RemoveOrUnlink(args []string, isRemove bool) error {
|
||||||
|
_ = isRemove // commands are identical, flag kept for clarity
|
||||||
|
|
||||||
|
flags, pkgSpec, err := ParsePackageFlags(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgName, relPath, err := ResolvePackageSpec(pkgSpec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := RepoPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgDir := filepath.Join(repo, pkgName)
|
||||||
|
configPath := filepath.Join(pkgDir, ConfigFileName)
|
||||||
|
cfg, err := LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetRoot, err := SelectTarget(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filesDir := filepath.Join(pkgDir, FilesDirName)
|
||||||
|
if _, err := os.Stat(filesDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if relPath == "" {
|
||||||
|
if err := RestorePackage(filesDir, targetRoot, flags.dryRun); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flags.dryRun {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(pkgDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("removed package %s\n", pkgName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := RestorePath(filesDir, targetRoot, relPath, flags.dryRun); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flags.dryRun {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(filepath.Join(filesDir, relPath)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("removed %s:%s\n", pkgName, relPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -19,8 +19,8 @@ func TestLinkFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test: create symlink
|
// Test: create symlink
|
||||||
if err := linkFile(src, dst); err != nil {
|
if err := LinkFile(src, dst); err != nil {
|
||||||
t.Errorf("linkFile failed: %v", err)
|
t.Errorf("LinkFile failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify symlink
|
// Verify symlink
|
||||||
@@ -33,8 +33,8 @@ func TestLinkFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test: idempotent - same symlink again should succeed
|
// Test: idempotent - same symlink again should succeed
|
||||||
if err := linkFile(src, dst); err != nil {
|
if err := LinkFile(src, dst); err != nil {
|
||||||
t.Errorf("linkFile second time failed: %v", err)
|
t.Errorf("LinkFile second time failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: conflict - different source should error
|
// Test: conflict - different source should error
|
||||||
@@ -43,8 +43,8 @@ func TestLinkFile(t *testing.T) {
|
|||||||
t.Fatalf("failed to create source2: %v", err)
|
t.Fatalf("failed to create source2: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := linkFile(src2, dst); err == nil {
|
if err := LinkFile(src2, dst); err == nil {
|
||||||
t.Error("linkFile should fail with conflicting symlink")
|
t.Error("LinkFile should fail with conflicting symlink")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: conflict - non-symlink file should error
|
// Test: conflict - non-symlink file should error
|
||||||
@@ -53,8 +53,8 @@ func TestLinkFile(t *testing.T) {
|
|||||||
t.Fatalf("failed to create regular file: %v", err)
|
t.Fatalf("failed to create regular file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := linkFile(src, dst2); err == nil {
|
if err := LinkFile(src, dst2); err == nil {
|
||||||
t.Error("linkFile should fail when target exists and is not a symlink")
|
t.Error("LinkFile should fail when target exists and is not a symlink")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,9 +102,9 @@ func TestFindStaleLinks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test: no stale links when sources exist
|
// Test: no stale links when sources exist
|
||||||
stale, err := findStaleLinks(filesDir, targetDir)
|
stale, err := FindStaleLinks(filesDir, targetDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("findStaleLinks failed: %v", err)
|
t.Errorf("FindStaleLinks failed: %v", err)
|
||||||
}
|
}
|
||||||
if len(stale) != 0 {
|
if len(stale) != 0 {
|
||||||
t.Errorf("expected 0 stale links, got %d", len(stale))
|
t.Errorf("expected 0 stale links, got %d", len(stale))
|
||||||
@@ -135,16 +135,16 @@ func TestFindStaleLinks_IgnoresNonRepoSymlinks(t *testing.T) {
|
|||||||
t.Fatalf("failed to create external file: %v", err)
|
t.Fatalf("failed to create external file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a symlink to external file (should be ignored by findStaleLinks)
|
// Create a symlink to external file (should be ignored by FindStaleLinks)
|
||||||
dst := filepath.Join(targetDir, "link.txt")
|
dst := filepath.Join(targetDir, "link.txt")
|
||||||
if err := os.Symlink(external, dst); err != nil {
|
if err := os.Symlink(external, dst); err != nil {
|
||||||
t.Fatalf("failed to create symlink: %v", err)
|
t.Fatalf("failed to create symlink: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: should not report external symlinks as stale
|
// Test: should not report external symlinks as stale
|
||||||
stale, err := findStaleLinks(filesDir, targetDir)
|
stale, err := FindStaleLinks(filesDir, targetDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("findStaleLinks failed: %v", err)
|
t.Errorf("FindStaleLinks failed: %v", err)
|
||||||
}
|
}
|
||||||
if len(stale) != 0 {
|
if len(stale) != 0 {
|
||||||
t.Errorf("expected 0 stale links (external symlinks ignored), got %d", len(stale))
|
t.Errorf("expected 0 stale links (external symlinks ignored), got %d", len(stale))
|
||||||
@@ -175,8 +175,8 @@ func TestApplyPackage(t *testing.T) {
|
|||||||
cfg := &packageConfig{} // empty config, no ignores
|
cfg := &packageConfig{} // empty config, no ignores
|
||||||
|
|
||||||
// Apply package
|
// Apply package
|
||||||
if err := applyPackage(filesDir, targetDir, cfg); err != nil {
|
if err := ApplyPackage(filesDir, targetDir, cfg); err != nil {
|
||||||
t.Errorf("applyPackage failed: %v", err)
|
t.Errorf("ApplyPackage failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify symlinks exist
|
// Verify symlinks exist
|
||||||
@@ -234,12 +234,12 @@ func TestApplyPackage_WithIgnores(t *testing.T) {
|
|||||||
cfg := &packageConfig{
|
cfg := &packageConfig{
|
||||||
compiledIgnores: []*regexp.Regexp{},
|
compiledIgnores: []*regexp.Regexp{},
|
||||||
}
|
}
|
||||||
re, _ := globToRegexp("cache/**")
|
re, _ := GlobToRegexp("cache/**")
|
||||||
cfg.compiledIgnores = append(cfg.compiledIgnores, re)
|
cfg.compiledIgnores = append(cfg.compiledIgnores, re)
|
||||||
|
|
||||||
// Apply package
|
// Apply package
|
||||||
if err := applyPackage(filesDir, targetDir, cfg); err != nil {
|
if err := ApplyPackage(filesDir, targetDir, cfg); err != nil {
|
||||||
t.Errorf("applyPackage failed: %v", err)
|
t.Errorf("ApplyPackage failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify main file is linked
|
// Verify main file is linked
|
||||||
@@ -282,8 +282,8 @@ func TestRestoreOne(t *testing.T) {
|
|||||||
|
|
||||||
// Restore (removes symlink, copies file)
|
// Restore (removes symlink, copies file)
|
||||||
filesAbs, _ := filepath.Abs(filesDir)
|
filesAbs, _ := filepath.Abs(filesDir)
|
||||||
if err := restoreOne(src, dst, filesAbs, false); err != nil {
|
if err := RestoreOne(src, dst, filesAbs, false); err != nil {
|
||||||
t.Errorf("restoreOne failed: %v", err)
|
t.Errorf("RestoreOne failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify symlink is gone
|
// Verify symlink is gone
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -8,18 +8,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// repoPath returns the absolute path to the sigil repository.
|
// RepoPath returns the absolute path to the sigil repository.
|
||||||
// It uses SIGIL_REPO environment variable if set, otherwise defaults to ~/.dotfiles.
|
// It uses SIGIL_REPO environment variable if set, otherwise defaults to ~/.dotfiles.
|
||||||
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))
|
||||||
}
|
}
|
||||||
return filepath.Abs(expandHome("~/.dotfiles"))
|
return filepath.Abs(ExpandHome("~/.dotfiles"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandHome expands a leading ~ to the user's home directory.
|
// ExpandHome expands a leading ~ to the user's home directory.
|
||||||
// Returns the original path unchanged if expansion fails or path doesn't start with ~.
|
// Returns the original path unchanged if expansion fails or path doesn't start with ~.
|
||||||
func expandHome(path string) string {
|
func ExpandHome(path string) string {
|
||||||
if path == "~" {
|
if path == "~" {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -37,9 +37,9 @@ func expandHome(path string) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// compressHome compresses the user's home directory path to ~.
|
// CompressHome compresses the user's home directory path to ~.
|
||||||
// Returns the original path if it doesn't start with the home directory.
|
// Returns the original path if it doesn't start with the home directory.
|
||||||
func compressHome(path string) string {
|
func CompressHome(path string) string {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return path
|
return path
|
||||||
@@ -56,9 +56,9 @@ func compressHome(path string) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitPackageSpec splits a package spec like "pkg:path/to/file" into (pkg, path).
|
// SplitPackageSpec splits a package spec like "pkg:path/to/file" into (pkg, path).
|
||||||
// If no colon is present, returns (spec, "", nil).
|
// If no colon is present, returns (spec, "", nil).
|
||||||
func splitPackageSpec(spec string) (string, string, error) {
|
func SplitPackageSpec(spec string) (string, string, error) {
|
||||||
if spec == "" {
|
if spec == "" {
|
||||||
return "", "", errors.New("missing package")
|
return "", "", errors.New("missing package")
|
||||||
}
|
}
|
||||||
@@ -82,8 +82,8 @@ func splitPackageSpec(spec string) (string, string, error) {
|
|||||||
|
|
||||||
// resolvePackageSpec resolves a package spec (name, path, or repo path) to (package, relative path).
|
// resolvePackageSpec resolves a package spec (name, path, or repo path) to (package, relative path).
|
||||||
// It handles specs like "pkg:path", absolute paths within the repo, and absolute paths in the target.
|
// It handles specs like "pkg:path", absolute paths within the repo, and absolute paths in the target.
|
||||||
func resolvePackageSpec(spec string) (string, string, error) {
|
func ResolvePackageSpec(spec string) (string, string, error) {
|
||||||
repo, err := repoPath()
|
repo, err := RepoPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
@@ -93,26 +93,26 @@ func resolvePackageSpec(spec string) (string, string, error) {
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
spec = expandHome(spec)
|
spec = ExpandHome(spec)
|
||||||
if filepath.IsAbs(spec) {
|
if filepath.IsAbs(spec) {
|
||||||
return resolvePathSpec(spec, repoAbs)
|
return ResolvePathSpec(spec, repoAbs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(spec, string(os.PathSeparator)) {
|
if strings.Contains(spec, string(os.PathSeparator)) {
|
||||||
return resolvePathSpec(spec, repoAbs)
|
return ResolvePathSpec(spec, repoAbs)
|
||||||
}
|
}
|
||||||
|
|
||||||
clean := filepath.Clean(spec)
|
clean := filepath.Clean(spec)
|
||||||
if strings.HasPrefix(clean, ".") || strings.HasPrefix(clean, string(os.PathSeparator)) {
|
if strings.HasPrefix(clean, ".") || strings.HasPrefix(clean, string(os.PathSeparator)) {
|
||||||
return resolvePathSpec(clean, repoAbs)
|
return ResolvePathSpec(clean, repoAbs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return splitPackageSpec(spec)
|
return SplitPackageSpec(spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolvePathSpec resolves an absolute path spec to a package and relative path.
|
// ResolvePathSpec resolves an absolute path spec to a package and relative path.
|
||||||
// It checks if the path is within the repo or matches a package's target directory.
|
// It checks if the path is within the repo or matches a package's target directory.
|
||||||
func resolvePathSpec(pathSpec, repoAbs string) (string, string, error) {
|
func ResolvePathSpec(pathSpec, repoAbs string) (string, string, error) {
|
||||||
absPath, err := filepath.Abs(pathSpec)
|
absPath, err := filepath.Abs(pathSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
@@ -126,7 +126,7 @@ func resolvePathSpec(pathSpec, repoAbs string) (string, string, error) {
|
|||||||
parts := strings.Split(rel, string(os.PathSeparator))
|
parts := strings.Split(rel, string(os.PathSeparator))
|
||||||
if len(parts) >= 1 {
|
if len(parts) >= 1 {
|
||||||
pkg := parts[0]
|
pkg := parts[0]
|
||||||
if len(parts) >= 2 && parts[1] == filesDirName {
|
if len(parts) >= 2 && parts[1] == FilesDirName {
|
||||||
relPath := filepath.Join(parts[2:]...)
|
relPath := filepath.Join(parts[2:]...)
|
||||||
return pkg, relPath, nil
|
return pkg, relPath, nil
|
||||||
}
|
}
|
||||||
@@ -144,19 +144,19 @@ func resolvePathSpec(pathSpec, repoAbs string) (string, string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pkgDir := filepath.Join(repoAbs, entry.Name())
|
pkgDir := filepath.Join(repoAbs, entry.Name())
|
||||||
configPath := filepath.Join(pkgDir, configFileName)
|
configPath := filepath.Join(pkgDir, ConfigFileName)
|
||||||
cfg, err := loadConfig(configPath)
|
cfg, err := LoadConfig(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
targetRoot, err := selectTarget(cfg)
|
targetRoot, err := SelectTarget(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errTargetDisabled) {
|
if errors.Is(err, ErrTargetDisabled) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
absTarget, err := filepath.Abs(expandHome(targetRoot))
|
absTarget, err := filepath.Abs(ExpandHome(targetRoot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -172,15 +172,15 @@ func resolvePathSpec(pathSpec, repoAbs string) (string, string, error) {
|
|||||||
return "", "", fmt.Errorf("could not resolve %s to a package", pathSpec)
|
return "", "", fmt.Errorf("could not resolve %s to a package", pathSpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findPackageByTarget finds a package name that has the given target root.
|
// FindPackageByTarget finds a package name that has the given target root.
|
||||||
// Returns empty string if no package matches.
|
// Returns empty string if no package matches.
|
||||||
func findPackageByTarget(repo, targetRoot string) (string, error) {
|
func FindPackageByTarget(repo, targetRoot string) (string, error) {
|
||||||
repoEntries, err := os.ReadDir(repo)
|
repoEntries, err := os.ReadDir(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
absTarget, err := filepath.Abs(expandHome(targetRoot))
|
absTarget, err := filepath.Abs(ExpandHome(targetRoot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -191,19 +191,19 @@ func findPackageByTarget(repo, targetRoot string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pkgDir := filepath.Join(repo, entry.Name())
|
pkgDir := filepath.Join(repo, entry.Name())
|
||||||
configPath := filepath.Join(pkgDir, configFileName)
|
configPath := filepath.Join(pkgDir, ConfigFileName)
|
||||||
cfg, err := loadConfig(configPath)
|
cfg, err := LoadConfig(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
target, err := selectTarget(cfg)
|
target, err := SelectTarget(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errTargetDisabled) {
|
if errors.Is(err, ErrTargetDisabled) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
absPkgTarget, err := filepath.Abs(expandHome(target))
|
absPkgTarget, err := filepath.Abs(ExpandHome(target))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -215,9 +215,9 @@ func findPackageByTarget(repo, targetRoot string) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// repoPathForTarget returns the repo file path that a target symlink points to.
|
// RepoPathForTarget returns the repo file path that a target symlink points to.
|
||||||
// Returns empty string if the target is not a symlink to the repo.
|
// Returns empty string if the target is not a symlink to the repo.
|
||||||
func repoPathForTarget(targetPath, repo string) (string, error) {
|
func RepoPathForTarget(targetPath, repo string) (string, error) {
|
||||||
info, err := os.Lstat(targetPath)
|
info, err := os.Lstat(targetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -252,7 +252,7 @@ func repoPathForTarget(targetPath, repo string) (string, error) {
|
|||||||
if len(parts) < 3 {
|
if len(parts) < 3 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
if parts[1] != filesDirName {
|
if parts[1] != FilesDirName {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -27,9 +27,9 @@ func TestExpandHome(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
t.Run(tt.input, func(t *testing.T) {
|
||||||
got := expandHome(tt.input)
|
got := ExpandHome(tt.input)
|
||||||
if got != tt.expected {
|
if got != tt.expected {
|
||||||
t.Errorf("expandHome(%q) = %q, want %q", tt.input, got, tt.expected)
|
t.Errorf("ExpandHome(%q) = %q, want %q", tt.input, got, tt.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -54,9 +54,9 @@ func TestCompressHome(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
t.Run(tt.input, func(t *testing.T) {
|
||||||
got := compressHome(tt.input)
|
got := CompressHome(tt.input)
|
||||||
if got != tt.expected {
|
if got != tt.expected {
|
||||||
t.Errorf("compressHome(%q) = %q, want %q", tt.input, got, tt.expected)
|
t.Errorf("CompressHome(%q) = %q, want %q", tt.input, got, tt.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -80,13 +80,13 @@ func TestSplitPackageSpec(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
t.Run(tt.input, func(t *testing.T) {
|
||||||
pkg, rel, err := splitPackageSpec(tt.input)
|
pkg, rel, err := SplitPackageSpec(tt.input)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("splitPackageSpec(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
t.Errorf("SplitPackageSpec(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if pkg != tt.wantPkg || rel != tt.wantRel {
|
if pkg != tt.wantPkg || rel != tt.wantRel {
|
||||||
t.Errorf("splitPackageSpec(%q) = (%q, %q), want (%q, %q)", tt.input, pkg, rel, tt.wantPkg, tt.wantRel)
|
t.Errorf("SplitPackageSpec(%q) = (%q, %q), want (%q, %q)", tt.input, pkg, rel, tt.wantPkg, tt.wantRel)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -150,13 +150,13 @@ func TestSelectTarget(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := selectTarget(tt.cfg)
|
got, err := SelectTarget(tt.cfg)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("selectTarget() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("SelectTarget() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Errorf("selectTarget() = %q, want %q", got, tt.want)
|
t.Errorf("SelectTarget() = %q, want %q", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -12,7 +12,7 @@ type packageFlags struct {
|
|||||||
dryRun bool
|
dryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePackageFlags(args []string) (packageFlags, string, error) {
|
func ParsePackageFlags(args []string) (packageFlags, string, error) {
|
||||||
flags := packageFlags{}
|
flags := packageFlags{}
|
||||||
var pkg string
|
var pkg string
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
@@ -43,7 +43,7 @@ func newReader() *bufio.Reader {
|
|||||||
return stdinReader
|
return stdinReader
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptWithDefault(label, def string) (string, error) {
|
func PromptWithDefault(label, def string) (string, error) {
|
||||||
reader := newReader()
|
reader := newReader()
|
||||||
if def != "" {
|
if def != "" {
|
||||||
fmt.Printf("%s [%s]: ", label, def)
|
fmt.Printf("%s [%s]: ", label, def)
|
||||||
@@ -62,7 +62,7 @@ func promptWithDefault(label, def string) (string, error) {
|
|||||||
return text, nil
|
return text, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptYesNo(message string, def bool) (bool, error) {
|
func PromptYesNo(message string, def bool) (bool, error) {
|
||||||
reader := newReader()
|
reader := newReader()
|
||||||
defLabel := "y/N"
|
defLabel := "y/N"
|
||||||
if def {
|
if def {
|
||||||
@@ -6,9 +6,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"sigil/internal/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "0.1.2"
|
const version = "0.1.3"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
@@ -69,7 +71,7 @@ func applyCmd(args []string) error {
|
|||||||
return fmt.Errorf("unknown flag %q", arg)
|
return fmt.Errorf("unknown flag %q", arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := repoPath()
|
repo, err := core.RepoPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,7 +89,7 @@ func applyCmd(args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pkgDir := filepath.Join(repo, entry.Name())
|
pkgDir := filepath.Join(repo, entry.Name())
|
||||||
configPath := filepath.Join(pkgDir, configFileName)
|
configPath := filepath.Join(pkgDir, core.ConfigFileName)
|
||||||
if _, err := os.Stat(configPath); err != nil {
|
if _, err := os.Stat(configPath); err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
continue
|
continue
|
||||||
@@ -95,20 +97,20 @@ func applyCmd(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := loadConfig(configPath)
|
cfg, err := core.LoadConfig(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
targetRoot, err := selectTarget(cfg)
|
targetRoot, err := core.SelectTarget(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errTargetDisabled) {
|
if errors.Is(err, core.ErrTargetDisabled) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filesDir := filepath.Join(pkgDir, filesDirName)
|
filesDir := filepath.Join(pkgDir, core.FilesDirName)
|
||||||
if _, err := os.Stat(filesDir); err != nil {
|
if _, err := os.Stat(filesDir); err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
continue
|
continue
|
||||||
@@ -116,11 +118,11 @@ func applyCmd(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := applyPackage(filesDir, targetRoot, cfg); err != nil {
|
if err := core.ApplyPackage(filesDir, targetRoot, cfg); err != nil {
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stale, err := findStaleLinks(filesDir, targetRoot)
|
stale, err := core.FindStaleLinks(filesDir, targetRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
@@ -132,11 +134,11 @@ func applyCmd(args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if prune {
|
if prune {
|
||||||
return removeLinks(stales, false)
|
return core.RemoveLinks(stales, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Stale links found: %d\n", len(stales))
|
fmt.Printf("Stale links found: %d\n", len(stales))
|
||||||
return handleStaleLinks(stales)
|
return core.HandleStaleLinks(stales)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addCmd(args []string) error {
|
func addCmd(args []string) error {
|
||||||
@@ -164,7 +166,7 @@ func addCmd(args []string) error {
|
|||||||
return fmt.Errorf("%s is a symlink; refusing to add", absPath)
|
return fmt.Errorf("%s is a symlink; refusing to add", absPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := repoPath()
|
repo, err := core.RepoPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -184,12 +186,12 @@ func addCmd(args []string) error {
|
|||||||
|
|
||||||
var pkgName string
|
var pkgName string
|
||||||
|
|
||||||
matchedPkg, err := findPackageByTarget(repo, defaultTarget)
|
matchedPkg, err := core.FindPackageByTarget(repo, defaultTarget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if matchedPkg != "" {
|
if matchedPkg != "" {
|
||||||
ok, err := promptYesNo(fmt.Sprintf("Merge into existing package %q?", matchedPkg), true)
|
ok, err := core.PromptYesNo(fmt.Sprintf("Merge into existing package %q?", matchedPkg), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -200,7 +202,7 @@ func addCmd(args []string) error {
|
|||||||
|
|
||||||
if pkgName == "" {
|
if pkgName == "" {
|
||||||
var err error
|
var err error
|
||||||
pkgName, err = promptWithDefault("Package name", defaultPkg)
|
pkgName, err = core.PromptWithDefault("Package name", defaultPkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -212,12 +214,12 @@ func addCmd(args []string) error {
|
|||||||
targetRootInput := defaultTarget
|
targetRootInput := defaultTarget
|
||||||
if pkgName != matchedPkg || matchedPkg == "" {
|
if pkgName != matchedPkg || matchedPkg == "" {
|
||||||
var err error
|
var err error
|
||||||
targetRootInput, err = promptWithDefault("Target path", defaultTarget)
|
targetRootInput, err = core.PromptWithDefault("Target path", defaultTarget)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetRoot, err := filepath.Abs(expandHome(targetRootInput))
|
targetRoot, err := filepath.Abs(core.ExpandHome(targetRootInput))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -230,14 +232,14 @@ func addCmd(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
filesDir := filepath.Join(pkgDir, filesDirName)
|
filesDir := filepath.Join(pkgDir, core.FilesDirName)
|
||||||
if err := os.MkdirAll(filesDir, 0o755); err != nil {
|
if err := os.MkdirAll(filesDir, 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := filepath.Join(pkgDir, configFileName)
|
configPath := filepath.Join(pkgDir, core.ConfigFileName)
|
||||||
if !pkgExists {
|
if !pkgExists {
|
||||||
if err := writeConfig(configPath, targetRoot); err != nil {
|
if err := core.WriteConfig(configPath, targetRoot); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,7 +247,7 @@ func addCmd(args []string) error {
|
|||||||
if pkgExists {
|
if pkgExists {
|
||||||
return errors.New("cannot merge a directory into an existing package yet")
|
return errors.New("cannot merge a directory into an existing package yet")
|
||||||
}
|
}
|
||||||
if err := moveDirContents(absPath, filesDir); err != nil {
|
if err := core.MoveDirContents(absPath, filesDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -271,86 +273,24 @@ func addCmd(args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := loadConfig(configPath)
|
cfg, err := core.LoadConfig(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return applyPackage(filesDir, targetRoot, cfg)
|
return core.ApplyPackage(filesDir, targetRoot, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unlinkCmd(args []string) error {
|
func unlinkCmd(args []string) error {
|
||||||
return removeOrUnlink(args, false)
|
return core.RemoveOrUnlink(args, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeCmd(args []string) error {
|
func removeCmd(args []string) error {
|
||||||
return removeOrUnlink(args, true)
|
return core.RemoveOrUnlink(args, true)
|
||||||
}
|
|
||||||
|
|
||||||
func removeOrUnlink(args []string, isRemove bool) error {
|
|
||||||
_ = isRemove // commands are identical, flag kept for clarity
|
|
||||||
|
|
||||||
flags, pkgSpec, err := parsePackageFlags(args)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgName, relPath, err := resolvePackageSpec(pkgSpec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := repoPath()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgDir := filepath.Join(repo, pkgName)
|
|
||||||
configPath := filepath.Join(pkgDir, configFileName)
|
|
||||||
cfg, err := loadConfig(configPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
targetRoot, err := selectTarget(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
filesDir := filepath.Join(pkgDir, filesDirName)
|
|
||||||
if _, err := os.Stat(filesDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if relPath == "" {
|
|
||||||
if err := restorePackage(filesDir, targetRoot, flags.dryRun); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if flags.dryRun {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := os.RemoveAll(pkgDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("removed package %s\n", pkgName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := restorePath(filesDir, targetRoot, relPath, flags.dryRun); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if flags.dryRun {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := os.RemoveAll(filepath.Join(filesDir, relPath)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("removed %s:%s\n", pkgName, relPath)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func statusCmd() error {
|
func statusCmd() error {
|
||||||
repo, err := repoPath()
|
repo, err := core.RepoPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -369,23 +309,23 @@ func statusCmd() error {
|
|||||||
|
|
||||||
totalPackages++
|
totalPackages++
|
||||||
pkgDir := filepath.Join(repo, entry.Name())
|
pkgDir := filepath.Join(repo, entry.Name())
|
||||||
configPath := filepath.Join(pkgDir, configFileName)
|
configPath := filepath.Join(pkgDir, core.ConfigFileName)
|
||||||
cfg, err := loadConfig(configPath)
|
cfg, err := core.LoadConfig(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
targetRoot, err := selectTarget(cfg)
|
targetRoot, err := core.SelectTarget(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errTargetDisabled) {
|
if errors.Is(err, core.ErrTargetDisabled) {
|
||||||
totalPackages-- // don't count disabled packages
|
totalPackages-- // don't count disabled packages
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filesDir := filepath.Join(pkgDir, filesDirName)
|
filesDir := filepath.Join(pkgDir, core.FilesDirName)
|
||||||
stale, err := findStaleLinks(filesDir, targetRoot)
|
stale, err := core.FindStaleLinks(filesDir, targetRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user