package main import ( "context" "encoding/json" "flag" "fmt" "os" "os/signal" "path" "regexp" "strings" "syscall" ) type Fields struct { Title string Season string Episode string } type MvNLn func(string, string) error func main() { ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT) defer can() flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) overridesS := flags.String("o", `{"title":"","season":"","episode":""}`, "overrides") ind := flags.String("i", "/dev/null", "in dir") outd := flags.String("o", "/dev/null", "out dir") if err := flags.Parse(os.Args[1:]); err != nil { panic(err) } var overrides Fields json.Unmarshal([]byte(*overridesS), &overrides) if err := Run(ctx, *outd, *ind, flags.Args(), overrides, RealMvNLn, ); err != nil { panic(err) } } func Run(ctx context.Context, outd, ind string, patterns []string, overrides Fields, mvNLn MvNLn) error { entries, err := os.ReadDir(ind) if err != nil { return err } for _, entry := range entries { if !entry.Type().IsRegular() { continue } if err := one(ctx, outd, path.Join(ind, entry.Name()), patterns, overrides, mvNLn); err != nil { return err } } return nil } func one(ctx context.Context, outd, inf string, patterns []string, overrides Fields, mvNLn MvNLn) error { f := path.Base(inf) for _, pattern := range patterns { re := regexp.MustCompile(pattern) if !re.MatchString(f) { continue } found := overrides groupNames := re.SubexpNames() groups := re.FindStringSubmatch(f) for i := 1; i < len(groupNames); i++ { v := groups[i] switch groupNames[i] { case "title": found.Title = v case "season": found.Season = v case "episode": found.Episode = v default: return fmt.Errorf("unexpected capture group %q", groupNames[i]) } } if found.Title == "" || found.Season == "" || found.Episode == "" { continue } found.Title = strings.Join(strings.Fields(found.Title), "_") return foundOne(ctx, outd, inf, found, mvNLn) } return nil } func foundOne(ctx context.Context, outd, inf string, fields Fields, mvNLn MvNLn) error { outf := path.Join(outd, fmt.Sprintf("%s_S%sE%s%s", fields.Title, fields.Season, fields.Episode, path.Ext(inf))) return mvNLn(outf, inf) } func RealMvNLn(outf, inf string) error { if stat, err := os.Stat(inf); err != nil || !stat.Mode().IsRegular() { return fmt.Errorf("cannot mv_n_ln(%s): (%v) mode=%v", inf, err, stat.Mode()) } if _, err := os.Stat(outf); err == nil { return nil // fmt.Errorf("conflict: %s already exists", path.Base(outf)) } if err := os.Rename(inf, outf); err != nil { return err } return os.Symlink(outf, inf) }