Re-write delete_dotfile_symlinks in golang
I'm currently quite unfamiliar with golang. As an exercise to help me onboard onto golang, and as a proof-of-concept to see if golang is a viable substitute for Python as a scripting language, I decided to port my delete_dotfile_symlinks to golang. In the process, renamed ./python -> ./scripts, which is a more accommodating name for a directory.
This commit is contained in:
parent
e8b47d5030
commit
6fd9947ec8
2 changed files with 117 additions and 88 deletions
|
@ -1,88 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# TODO: Prefer a proper Python module doc here.
|
||||
# Find and delete all symlinks to the dotfiles defined in $BRIEFCASE.
|
||||
#
|
||||
# Oftentimes I corrupt the state of my dotfiles. The intention with this script
|
||||
# is to write some tooling to help me better manage my dotfile cleanliness. An
|
||||
# example workflow might look like:
|
||||
#
|
||||
# ```shell
|
||||
# > ./delete_dotfile_symlinks.py --audit
|
||||
# > ./delete_dotfile_symlinks.py --seriously
|
||||
# > cd ..
|
||||
# > make install
|
||||
# ```
|
||||
|
||||
################################################################################
|
||||
# Dependencies
|
||||
################################################################################
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from os.path import expanduser
|
||||
|
||||
################################################################################
|
||||
# Library
|
||||
################################################################################
|
||||
|
||||
def homify(path):
|
||||
"""Prefer ~ instead of the absolute `path`."""
|
||||
home = expanduser('~')
|
||||
return path.replace(home, '~')
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Remove symlinks to my managed dotfiles.')
|
||||
parser.add_argument('--audit', dest='audit', action='store_true', help='Output all symlinks that would be deleted. This is the default behavior. This option is mutually exclusive with the --seriously option.')
|
||||
parser.add_argument('--seriously', dest='seriously', action='store_true', help='Actually delete the symlinks. This option is mutually exclusive with the --audit option.')
|
||||
parser.add_argument('--repo-name', dest='name', default='briefcase', help='The name of the repository. Usually "briefcase" or "dotfiles".')
|
||||
parser.add_argument('--device-only', dest='device_only', action='store_true', help='Only output the device-specific dotfiles.')
|
||||
args = parser.parse_args()
|
||||
|
||||
# TODO: Instead of doing this manually, is this something that argparse supports?
|
||||
if not args.audit and not args.seriously:
|
||||
print('Either --audit or --seriously must be passed. See --help for more information.')
|
||||
# TODO: What's the proper exit code here?
|
||||
sys.exit(1)
|
||||
|
||||
# These two options are mutually exclusive.
|
||||
assert args.audit != args.seriously
|
||||
|
||||
count = 0
|
||||
|
||||
for cwd, dirs, files in os.walk(expanduser("~")):
|
||||
# Skip this repository.
|
||||
if args.name in cwd:
|
||||
continue
|
||||
for file in files:
|
||||
source = os.path.join(cwd, file)
|
||||
if os.path.islink(source):
|
||||
dest = os.readlink(source)
|
||||
# We won't know certainly if the link points to a dotfile that I manage
|
||||
# with this simple test.
|
||||
if args.device_only:
|
||||
# TODO: Change 'desktop' with a lookup of hostname to device name.
|
||||
if 'configs/desktop' in dest:
|
||||
if args.audit:
|
||||
print('{} -> {}'.format(homify(source), homify(dest)))
|
||||
elif args.seriously:
|
||||
print('rm {}'.format(source))
|
||||
os.remove(source)
|
||||
count += 1
|
||||
elif args.name in dest:
|
||||
if args.audit:
|
||||
print('{} -> {}'.format(homify(source), homify(dest)))
|
||||
elif args.seriously:
|
||||
print('rm {}'.format(source))
|
||||
os.remove(source)
|
||||
count += 1
|
||||
|
||||
if args.audit:
|
||||
print('Would have deleted {} symlinks.'.format(count))
|
||||
elif args.seriously:
|
||||
print('Successfully deleted {} symlinks.'.format(count))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
117
scripts/delete_dotfile_symlinks.go
Normal file
117
scripts/delete_dotfile_symlinks.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
// Find and delete all symlinks to the dotfiles defined in $BRIEFCASE.
|
||||
//
|
||||
// Oftentimes I corrupt the state of my dotfiles. The intention with this script
|
||||
// is to write some tooling to help me better manage my dotfile cleanliness. An
|
||||
// example workflow might look like:
|
||||
//
|
||||
// ```shell
|
||||
// > go run delete_dotfile_symlinks.go --audit
|
||||
// > go run delete_dotfile_symlinks.go --seriously
|
||||
// > cd ..
|
||||
// > make install
|
||||
// ```
|
||||
//
|
||||
// Outstanding TODOs:
|
||||
// - Package this with <depot>buildGo.nix.
|
||||
// - How can this be run as script without `go run`? She-bang at the top?
|
||||
// - See TODOs within this package.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Wanted for go tooling:
|
||||
// 1. jump-to-def
|
||||
// 2. documentation at point
|
||||
// 3. autocompletion
|
||||
|
||||
// TODO: Consider adding this to a utils.go package.
|
||||
func failOn(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider adding this to a utils.go package.
|
||||
func isSymlink(m os.FileMode) bool {
|
||||
return m&os.ModeSymlink != 0
|
||||
}
|
||||
|
||||
var hostnames = map[string]string{
|
||||
os.Getenv("DESKTOP"): "desktop",
|
||||
os.Getenv("LAPTOP"): "work_laptop",
|
||||
os.Getenv("CLOUDTOP"): "cloudtop",
|
||||
}
|
||||
|
||||
func main() {
|
||||
audit := flag.Bool("audit", false, "Output all symlinks that would be deleted. This is the default behavior. This option is mutually exclusive with the --seriously option.")
|
||||
seriously := flag.Bool("seriously", false, "Actually delete the symlinks. This option is mutually exclusive with the --audit option.")
|
||||
repoName := flag.String("repo-name", "briefcase", "The name of the repository.")
|
||||
deviceOnly := flag.Bool("device-only", false, "Only output the device-specific dotfiles.")
|
||||
flag.Parse()
|
||||
|
||||
if !*audit && !*seriously {
|
||||
log.Fatal(errors.New("Either -audit or -seriously needs to be set."))
|
||||
}
|
||||
if *audit == *seriously {
|
||||
log.Fatal(errors.New("Arguments -audit and -seriously are mutually exclusive"))
|
||||
}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
failOn(err)
|
||||
count := 0
|
||||
|
||||
err = filepath.Walk(home, func(path string, info os.FileInfo, err error) error {
|
||||
if isSymlink(info.Mode()) {
|
||||
dest, err := os.Readlink(path)
|
||||
failOn(err)
|
||||
|
||||
var predicate func(string) bool
|
||||
|
||||
if *deviceOnly {
|
||||
predicate = func(dest string) bool {
|
||||
var hostname string
|
||||
hostname, err = os.Hostname()
|
||||
failOn(err)
|
||||
seeking, ok := hostnames[hostname]
|
||||
if !ok {
|
||||
log.Fatal(fmt.Sprintf("Hostname \"%s\" not supported in the hostnames map.", hostname))
|
||||
}
|
||||
return strings.Contains(dest, *repoName) && strings.Contains(dest, seeking)
|
||||
}
|
||||
} else {
|
||||
predicate = func(dest string) bool {
|
||||
return strings.Contains(dest, *repoName)
|
||||
}
|
||||
}
|
||||
|
||||
if predicate(dest) {
|
||||
if *audit {
|
||||
fmt.Printf("%s -> %s\n", path, dest)
|
||||
} else if *seriously {
|
||||
fmt.Printf("rm %s\n", path)
|
||||
err = os.Remove(path)
|
||||
failOn(err)
|
||||
}
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
failOn(err)
|
||||
if *audit {
|
||||
fmt.Printf("Would have deleted %d symlinks.\n", count)
|
||||
} else if *seriously {
|
||||
fmt.Printf("Successfully deleted %d symlinks.\n", count)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
Loading…
Reference in a new issue