package main import ( "fmt" "log" "os" "path" "regexp" "strconv" "strings" ) type LinkInstruction struct { Source string Target string Force bool Hard bool } func (instruction *LinkInstruction) String() string { return fmt.Sprintf("%s%s%s%s%s%s%s%s%s%s%s", SourceColor, instruction.Source, DefaultColor, deliminer, TargetColor, instruction.Target, DefaultColor, deliminer, strconv.FormatBool(instruction.Force), deliminer, strconv.FormatBool(instruction.Hard)) } func ParseInstruction(line string) (LinkInstruction, error) { line = strings.TrimSpace(line) parts := strings.Split(line, deliminer) instruction := LinkInstruction{} if len(parts) < 2 { return instruction, fmt.Errorf("invalid format - not enough parameters (must have at least source and target)") } instruction.Source = strings.TrimSpace(parts[0]) instruction.Target = strings.TrimSpace(parts[1]) instruction.Force = false if len(parts) > 2 { res, _ := regexp.MatchString(`^(?i)\s*T|TRUE\s*$`, parts[2]) instruction.Force = res } if len(parts) > 3 { res, _ := regexp.MatchString(`^(?i)\s*T|TRUE\s*$`, parts[3]) instruction.Hard = res } instruction.Source, _ = ConvertHome(instruction.Source) instruction.Target, _ = ConvertHome(instruction.Target) instruction.Source = NormalizePath(instruction.Source) instruction.Target = NormalizePath(instruction.Target) return instruction, nil } func (instruction *LinkInstruction) RunSync() error { if !FileExists(instruction.Source) { return fmt.Errorf("instruction source %s%s%s does not exist", SourceColor, instruction.Source, DefaultColor) } if AreSame(instruction.Source, instruction.Target) { log.Printf("Source %s%s%s and target %s%s%s are the same, %snothing to do...%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, PathColor, DefaultColor) return nil } if FileExists(instruction.Target) { if instruction.Force { isSymlink, err := IsSymlink(instruction.Target) if err != nil { return fmt.Errorf("could not determine whether %s%s%s is a sym link or not, stopping; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) } if isSymlink { log.Printf("Removing symlink at %s%s%s", TargetColor, instruction.Target, DefaultColor) err = os.Remove(instruction.Target) if err != nil { return fmt.Errorf("failed deleting %s%s%s due to %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) } } else { return fmt.Errorf("refusing to delte actual (non symlink) file %s%s%s", TargetColor, instruction.Target, DefaultColor) } } else { return fmt.Errorf("target %s%s%s exists - handle manually or set the 'forced' flag (3rd field)", TargetColor, instruction.Target, DefaultColor) } } err := os.Symlink(instruction.Source, instruction.Target) if err != nil { return fmt.Errorf("failed creating symlink between %s%s%s and %s%s%s with error %s%+v%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) } log.Printf("Created symlink between %s%s%s and %s%s%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor) return nil } func (instruction *LinkInstruction) RunAsync(status chan (error)) { defer close(status) if !FileExists(instruction.Source) { status <- fmt.Errorf("instruction source %s%s%s does not exist", SourceColor, instruction.Source, DefaultColor) return } if AreSame(instruction.Source, instruction.Target) { status <- fmt.Errorf("source %s%s%s and target %s%s%s are the same, %snothing to do...%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, PathColor, DefaultColor) return } if FileExists(instruction.Target) { if instruction.Force { isSymlink, err := IsSymlink(instruction.Target) if err != nil { status <- fmt.Errorf("could not determine whether %s%s%s is a sym link or not, stopping; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) return } if instruction.Hard { info, err := os.Stat(instruction.Target) if err != nil { status <- fmt.Errorf("could not stat %s%s%s, stopping; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) return } if info.Mode().IsRegular() && info.Name() == path.Base(instruction.Source) { log.Printf("Overwriting existing file %s%s%s", TargetColor, instruction.Target, DefaultColor) err := os.Remove(instruction.Target) if err != nil { status <- fmt.Errorf("could not remove existing file %s%s%s; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) return } } } if isSymlink { log.Printf("Removing symlink at %s%s%s", TargetColor, instruction.Target, DefaultColor) err = os.Remove(instruction.Target) if err != nil { status <- fmt.Errorf("failed deleting %s%s%s due to %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) return } } else { status <- fmt.Errorf("refusing to delte actual (non symlink) file %s%s%s", TargetColor, instruction.Target, DefaultColor) return } } else { status <- fmt.Errorf("target %s%s%s exists - handle manually or set the 'forced' flag (3rd field)", TargetColor, instruction.Target, DefaultColor) return } } var err error if instruction.Hard { err = os.Link(instruction.Source, instruction.Target) } else { err = os.Symlink(instruction.Source, instruction.Target) } if err != nil { status <- fmt.Errorf("failed creating symlink between %s%s%s and %s%s%s with error %s%+v%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) return } log.Printf("Created symlink between %s%s%s and %s%s%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor) status <- nil }