Skip to content

Commit

Permalink
Merge pull request #14 from sigmonsays/clean
Browse files Browse the repository at this point in the history
add ability to delete dangling symlinks
  • Loading branch information
sigmonsays authored Dec 3, 2022
2 parents ff5a968 + 21d9673 commit 07eba59
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 22 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
31 changes: 13 additions & 18 deletions cleanup.go → clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -40,8 +40,16 @@ 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 {

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
Expand Down Expand Up @@ -70,16 +78,3 @@ func (me *Cleanup) 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
}
13 changes: 13 additions & 0 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(10000)
if err != nil {
return nil, err
}
return names, nil
}
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
96 changes: 96 additions & 0 deletions link.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ func (me *Link) RunConfig(opts *LinkOptions, cfg *AppConfig) error {
if err != nil {
return err
}
err = CleanLinks(opts, cfg.Clean, run.HomeDir)
if err != nil {
return err
}

return nil
}
Expand Down Expand Up @@ -236,3 +240,95 @@ func RunScripts(opts *LinkOptions, run *Run, stype string) error {

return nil
}

// 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)

log.Infof("Remove dangling symlink %s", fullpath)
os.Remove(fullpath)
}

}
}
return nil
}
8 changes: 4 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
}

0 comments on commit 07eba59

Please sign in to comment.