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[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.*) - (?P[0-9]+).*`,
+ `^(?P.*) S(?P[0-9]+)E(?P[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.) (?P.) (?P.)"]
+ }`)
+ 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
+}