From b727756a13d42836582c188cde33a78e9e5ab4ff Mon Sep 17 00:00:00 2001 From: Sig Lange Date: Sat, 3 Dec 2022 09:32:41 -0800 Subject: [PATCH 1/5] rename to clean --- cleanup.go => clean.go | 10 +++++----- config.go | 1 + link.go | 8 ++++++++ main.go | 8 ++++---- 4 files changed, 18 insertions(+), 9 deletions(-) rename cleanup.go => clean.go (85%) diff --git a/cleanup.go b/clean.go similarity index 85% rename from cleanup.go rename to clean.go index bdd3d35..03e1b8e 100644 --- a/cleanup.go +++ b/clean.go @@ -6,15 +6,15 @@ import ( "github.com/urfave/cli/v2" ) -type Cleanup struct { +type Clean struct { ctx *Context } -func (me *Cleanup) Flags() []cli.Flag { +func (me *Clean) Flags() []cli.Flag { return []cli.Flag{} } -func (me *Cleanup) Run(c *cli.Context) error { +func (me *Clean) Run(c *cli.Context) error { configfiles := me.ctx.getConfigFiles(c) for _, filename := range configfiles { @@ -27,7 +27,7 @@ func (me *Cleanup) Run(c *cli.Context) error { return nil } -func (me *Cleanup) RunFile(path string) error { +func (me *Clean) RunFile(path string) error { log.Tracef("runfile %s", path) cfg := GetDefaultConfig() err := cfg.LoadYaml(path) @@ -40,7 +40,7 @@ func (me *Cleanup) RunFile(path string) error { return me.RunConfig(cfg) } -func (me *Cleanup) RunConfig(cfg *AppConfig) error { +func (me *Clean) RunConfig(cfg *AppConfig) error { run, err := CompileRun(cfg.Symlinks, cfg.Script) if err != nil { diff --git a/config.go b/config.go index 4131b37..84b3408 100644 --- a/config.go +++ b/config.go @@ -10,6 +10,7 @@ import ( // main configuration structure type AppConfig struct { + Clean []string `yaml:"clean"` Mkdirs []string `yaml:"mkdirs"` Symlinks map[string]string `yaml:"symlinks"` Script []*Script `yaml:"script"` diff --git a/link.go b/link.go index 1d55d38..2d0b864 100644 --- a/link.go +++ b/link.go @@ -96,6 +96,10 @@ func (me *Link) RunConfig(opts *LinkOptions, cfg *AppConfig) error { if err != nil { return err } + err = CleanLinks(opts, run) + if err != nil { + return err + } return nil } @@ -236,3 +240,7 @@ func RunScripts(opts *LinkOptions, run *Run, stype string) error { return nil } + +func CleanLinks(opts *LinkOptions, run *Run) error { + return nil +} diff --git a/main.go b/main.go index 3b566b2..5faef4f 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,7 @@ func main() { link := &Link{ctx} unlink := &Unlink{ctx} status := &Status{ctx} - cleanup := &Cleanup{ctx} + clean := &Clean{ctx} app := &cli.App{ Name: "dotbot", @@ -66,10 +66,10 @@ func main() { Flags: status.Flags(), }) ctx.addCommand(&cli.Command{ - Name: "cleanup", + Name: "clean", Usage: "show unreferenced files", - Action: cleanup.Run, - Flags: cleanup.Flags(), + Action: clean.Run, + Flags: clean.Flags(), }) app.Run(os.Args) } From 6f2a241e79f74135c5769673250b38c7f56f387d Mon Sep 17 00:00:00 2001 From: Sig Lange Date: Sat, 3 Dec 2022 12:19:27 -0800 Subject: [PATCH 2/5] refactor clean --- clean.go | 21 ++++++++------------- common.go | 13 +++++++++++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/clean.go b/clean.go index 03e1b8e..8f3ac97 100644 --- a/clean.go +++ b/clean.go @@ -42,6 +42,14 @@ func (me *Clean) RunFile(path string) error { func (me *Clean) RunConfig(cfg *AppConfig) error { + err := me.CleanUnreferenced(cfg) + if err != nil { + log.Warnf("Clean unreferenced %s", err) + } + + return nil +} +func (me *Clean) CleanUnreferenced(cfg *AppConfig) error { run, err := CompileRun(cfg.Symlinks, cfg.Script) if err != nil { return err @@ -70,16 +78,3 @@ func (me *Clean) RunConfig(cfg *AppConfig) error { return nil } - -func ListDir(path string) ([]string, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - names, err := f.Readdirnames(1000) - if err != nil { - return nil, err - } - return names, nil -} diff --git a/common.go b/common.go index 7c1f7b1..4677905 100644 --- a/common.go +++ b/common.go @@ -28,3 +28,16 @@ func (me *Context) getConfigFiles(c *cli.Context) []string { } return ret } + +func ListDir(path string) ([]string, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + names, err := f.Readdirnames(1000) + if err != nil { + return nil, err + } + return names, nil +} From c061f95d09f834d3912fb4dd7800f27c08695d3a Mon Sep 17 00:00:00 2001 From: Sig Lange Date: Sat, 3 Dec 2022 15:43:41 -0800 Subject: [PATCH 3/5] detect dangling --- common.go | 2 +- link.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/common.go b/common.go index 4677905..736db60 100644 --- a/common.go +++ b/common.go @@ -35,7 +35,7 @@ func ListDir(path string) ([]string, error) { return nil, err } defer f.Close() - names, err := f.Readdirnames(1000) + names, err := f.Readdirnames(10000) if err != nil { return nil, err } diff --git a/link.go b/link.go index 2d0b864..7a06d4f 100644 --- a/link.go +++ b/link.go @@ -96,7 +96,7 @@ func (me *Link) RunConfig(opts *LinkOptions, cfg *AppConfig) error { if err != nil { return err } - err = CleanLinks(opts, run) + err = CleanLinks(opts, cfg.Clean, run.HomeDir) if err != nil { return err } @@ -241,6 +241,90 @@ func RunScripts(opts *LinkOptions, run *Run, stype string) error { return nil } -func CleanLinks(opts *LinkOptions, run *Run) error { +// go through each glob and ensure its a directory +func CleanLinks(opts *LinkOptions, dirs []string, homedir string) error { + for _, glob_pattern := range dirs { + if strings.HasPrefix(glob_pattern, "~") { + glob_pattern = filepath.Join(homedir, glob_pattern[1:]) + } + matches, err := filepath.Glob(glob_pattern) + if err != nil { + return err + } + err = CleanLinksGlob(opts, glob_pattern, matches) + if err != nil { + log.Warnf("Glob %s: %s", glob_pattern, err) + continue + } + } + + return nil +} + +func CleanLinksGlob(opts *LinkOptions, dir_pattern string, matches []string) error { + log.Tracef("CleanLinksGlob %s", dir_pattern) + for _, filename := range matches { + log.Tracef("filename %s", filename) + + st, err := os.Stat(filename) + if err != nil { + log.Warnf("Stat %s: %s", filename, err) + continue + } + if st.IsDir() == false { + continue // we only want directories + } + + dir := filename + + ls, err := ListDir(dir) + if err != nil { + log.Warnf("ListDir %s: %s", dir, err) + continue + } + + // go through each file and ensure it's a symlink and valid + for _, filename := range ls { + fullpath := filepath.Join(dir, filename) + log.Tracef("clean symlink %s", fullpath) + + // stat the link + st, err := os.Lstat(fullpath) + if err != nil { + log.Warnf("Lstat %s: %s", fullpath, err) + } + is_symlink := false + if err == nil && st.Mode()&os.ModeSymlink == os.ModeSymlink { + is_symlink = true + } + + // stat the file (and what the link points to) + _, err = os.Stat(fullpath) + stat_ok := (err == nil) + + // if it's a symlink, get what it points to + var link string + if is_symlink { + link, err = os.Readlink(fullpath) + if err != nil { + log.Warnf("Readlink %s", err) + } + } + log.Tracef("points to %s", link) + + // check if the symlink points to something invalid + dangling := false + if is_symlink == true && link != "" { + _, err := os.Stat(link) + if err != nil { + dangling = true + } + } + if dangling && stat_ok == false { + log.Tracef("dangling symlink %s is invalid, points to %s", + fullpath, link) + } + } + } return nil } From 556b5e3a68617ee3c6ccdceaa668cec514e7780c Mon Sep 17 00:00:00 2001 From: Sig Lange Date: Sat, 3 Dec 2022 15:45:35 -0800 Subject: [PATCH 4/5] actually remove the dangler link --- link.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/link.go b/link.go index 7a06d4f..88a55ad 100644 --- a/link.go +++ b/link.go @@ -323,7 +323,11 @@ func CleanLinksGlob(opts *LinkOptions, dir_pattern string, matches []string) err if dangling && stat_ok == false { log.Tracef("dangling symlink %s is invalid, points to %s", fullpath, link) + + log.Infof("Remove dangling symlink %s", fullpath) + os.Remove(fullpath) } + } } return nil From 21d9673dd5331ddaa11a62459f7cc1b3be12f3dc Mon Sep 17 00:00:00 2001 From: Sig Lange Date: Sat, 3 Dec 2022 15:49:09 -0800 Subject: [PATCH 5/5] docs --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f7fbc15..e6b1a9f 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,14 @@ files can be specified by passing -c with a file multiple times. If no configuration file is provided, dotbot.yaml is assumed if it exists in the current directory. +the clean block indicates directories to clean up broken symlinks (aka, dangling symlinks) +the tilde (`~`) is automatically expanded to the uses home directory. The path is evaluated +as a glob so wildcards may be used. + Sample configuration + clean: + - '~' mkdirs: - ~/asdf symlinks: