From daa520de7deba1e3c27c42107255ddf9e4833a48 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Sat, 5 Apr 2025 00:34:01 -0600 Subject: [PATCH] test recursive and run as go run ./ r --- go.mod | 2 + go.sum | 3 ++ main.go | 112 ++++++++++++++++++++++++++++++++++++++++++--------- main_test.go | 102 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 195 insertions(+), 24 deletions(-) create mode 100644 go.sum diff --git a/go.mod b/go.mod index 6f0729d..e62e82c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module gitea/show-ingestion go 1.23.3 + +require gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4bc0337 --- /dev/null +++ b/go.sum @@ -0,0 +1,3 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 3bc5425..cb44a5f 100644 --- a/main.go +++ b/main.go @@ -5,13 +5,15 @@ import ( "encoding/json" "flag" "fmt" - "io" + "io/fs" "os" "os/signal" "path" "regexp" "strings" "syscall" + + yaml "gopkg.in/yaml.v3" ) type Fields struct { @@ -26,19 +28,65 @@ func main() { ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT) defer can() - if len(os.Args) < 2 { - if err := Recursive(ctx); err != nil { - panic(err) - } - } else { - if err := Main(ctx); err != nil { - panic(err) - } + foo := Main + if len(os.Args) == 2 && os.Args[1] == "r" { + foo = Recursive + } + if err := foo(ctx); err != nil { + panic(err) } } func Recursive(ctx context.Context) error { - return io.EOF + q := []string{"./"} + for len(q) > 0 { + d := q[0] + q = q[1:] + + p := path.Join(d, ".show-ingestion.yaml") + if _, err := os.Stat(p); err != nil { + } else if err := func() error { + var y struct { + C Fields + O string + D bool + P []string + } + b, _ := os.ReadFile(path.Join(d, ".show-ingestion.yaml")) + if err := yaml.Unmarshal(b, &y); err != nil { + return fmt.Errorf("%s: %w", p, err) + } + + was, err := os.Getwd() + if err != nil { + return err + } + + if err := os.Chdir(d); err != nil { + return err + } + defer os.Chdir(was) + + if err := Run(ctx, y.O, "./", y.P, y.C, y.D); err != nil { + return err + } + + return os.Chdir(was) + }(); err != nil { + return fmt.Errorf("%s: %w", p, err) + } + + entries, err := readDir(d) + if err != nil { + return err + } + for _, entry := range entries { + if entry.IsDir() { + q = append(q, path.Join(d, entry.Name())) + } + } + } + return nil } func Main(ctx context.Context) error { @@ -54,24 +102,34 @@ func Main(ctx context.Context) error { var overrides Fields json.Unmarshal([]byte(*overridesS), &overrides) - mvNLn := RealMvNLn - if *dry { - mvNLn = DryMvNLn() - } - return Run(ctx, *outd, *ind, - append(flags.Args(), - `^\[[^\]]*\] (?P.*) - (?P<episode>[0-9]+) .*`, + flags.Args(), + overrides, + *dry, + ) +} + +func Run(ctx context.Context, outd, ind string, patterns []string, overrides Fields, dry bool) error { + mvNLn := RealMvNLn + if dry { + mvNLn = DryMvNLn() + } + return RunWith(ctx, + outd, + ind, + append(patterns, + `^\[[^\]]*\] (?P<title>.*) - (?P<episode>[0-9]+).*`, + `^(?P<title>.*) S(?P<season>[0-9]+)E(?P<episode>[0-9]+).*`, ), overrides, mvNLn, ) } -func Run(ctx context.Context, outd, ind string, patterns []string, overrides Fields, mvNLn MvNLn) error { - entries, err := os.ReadDir(ind) +func RunWith(ctx context.Context, outd, ind string, patterns []string, overrides Fields, mvNLn MvNLn) error { + entries, err := readDir(ind) if err != nil { return err } @@ -131,7 +189,10 @@ func RealMvNLn(outf, inf string) error { 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)) + return nil + } + if err := os.MkdirAll(path.Dir(outf), os.ModePerm); err != nil { + return err } if err := os.Rename(inf, outf); err != nil { return err @@ -155,3 +216,14 @@ func DryMvNLn() func(string, string) error { return nil } } + +func readDir(d string) ([]fs.DirEntry, error) { + entries, err := os.ReadDir(d) + result := []fs.DirEntry{} + for _, entry := range entries { + if !strings.HasPrefix(entry.Name(), ".") { + result = append(result, entry) + } + } + return result, err +} diff --git a/main_test.go b/main_test.go index 7f7d0e4..1755a3c 100644 --- a/main_test.go +++ b/main_test.go @@ -28,7 +28,7 @@ func TestRunChoosesOne(t *testing.T) { "Australian_Survivor_S12E12.mkv": false, } - if err := main.Run(context.Background(), + if err := main.RunWith(context.Background(), outd, ind, []string{ @@ -53,7 +53,7 @@ func TestRunChoosesOne(t *testing.T) { } } -func TestRun(t *testing.T) { +func TestRunWith(t *testing.T) { cases := map[string]struct { given []string patterns []string @@ -106,9 +106,9 @@ func TestRun(t *testing.T) { } outd := t.TempDir() - if err := main.Run(context.Background(), outd, ind, c.patterns, c.overrides, main.RealMvNLn); err != nil { + if err := main.RunWith(context.Background(), outd, ind, c.patterns, c.overrides, main.RealMvNLn); err != nil { t.Fatal("err on first run:", err) - } else if err := main.Run(context.Background(), outd, ind, c.patterns, c.overrides, main.RealMvNLn); err != nil { + } else if err := main.RunWith(context.Background(), outd, ind, c.patterns, c.overrides, main.RealMvNLn); err != nil { t.Fatal("err on second run:", err) } @@ -147,3 +147,97 @@ func TestRun(t *testing.T) { }) } } + +func TestRecursive(t *testing.T) { + was, _ := os.Getwd() + t.Cleanup(func() { os.Chdir(was) }) + os.Chdir(t.TempDir()) + + outd := t.TempDir() + os.MkdirAll(path.Join(outd, "A"), os.ModePerm) + + // use config + write("./showA/.show-ingestion.yaml", `{ + "c": { + "title": "A", + "season": "A", + "episode": "A" + }, + "p": [".*"], + "o": "`+outd+`/A" + }`) + write("./showA/file.a") + + // parse files + write("./showB/.show-ingestion.yaml", `{ + "o": "`+outd+`/B", + "p": [] + }`) + write("./showB/title S01E02.b") + + // use file pattern + write("./dirA/showC/.show-ingestion.yaml", `{ + "o": "`+outd+`/C", + "p": ["^(?P<title>.) (?P<season>.) (?P<episode>.)"] + }`) + write("./dirA/showC/t s e.c") + + // dry run + write("./dirA/showD/.show-ingestion.yaml", `{ + "o": "`+outd+`/D", + "d": true + }`) + write("./dirA/showD/title S02E04.d") + + // not configured + os.MkdirAll("./dirB/showE", os.ModePerm) + write("./dirB/showE/title S03E06.e") + + if err := main.Recursive(context.Background()); err != nil { + t.Fatal(err) + } + + exists(t, path.Join(outd, "A", "A_SAEA.a")) + exists(t, path.Join(outd, "B", "title_S01E02.b")) + exists(t, path.Join(outd, "C", "t_SsEe.c")) + notExists(t, path.Join(outd, "D", "title_S02E04.d")) + notExists(t, path.Join(outd, "title_S03E06.e")) +} + +func write(f string, b ...string) { + if len(b) == 0 { + b = append(b, "") + } + + os.MkdirAll(path.Dir(f), os.ModePerm) + os.WriteFile(f, []byte(b[0]), os.ModePerm) +} + +func exists(t *testing.T, p string) { + if _, err := os.Stat(p); os.IsNotExist(err) { + d := path.Dir(path.Dir(p)) + t.Errorf("expected %s of %s (%+v)", path.Base(p), d, ls(d)) + } +} + +func notExists(t *testing.T, p string) { + if _, err := os.Stat(p); !os.IsNotExist(err) { + d := path.Dir(path.Dir(p)) + t.Errorf("unexpected %s of %s (%+v)", path.Base(p), d, ls(d)) + } +} + +func ls(d string) []string { + result := []string{} + entries, _ := os.ReadDir(d) + for _, entry := range entries { + p := path.Join(d, entry.Name()) + if entry.IsDir() { + result = append(result, ls(p)...) + } else { + result = append(result, p) + } + } + slices.Sort(result) + return result +}