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…
	
	Add table
		Add a link
		
	
		Reference in a new issue