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