test recursive and run as go run ./ r

main
Bel LaPointe 2025-04-05 00:34:01 -06:00
parent 6af1f231df
commit daa520de7d
4 changed files with 195 additions and 24 deletions

2
go.mod
View File

@ -1,3 +1,5 @@
module gitea/show-ingestion module gitea/show-ingestion
go 1.23.3 go 1.23.3
require gopkg.in/yaml.v3 v3.0.1 // indirect

3
go.sum Normal file
View File

@ -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=

106
main.go
View File

@ -5,13 +5,15 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"io" "io/fs"
"os" "os"
"os/signal" "os/signal"
"path" "path"
"regexp" "regexp"
"strings" "strings"
"syscall" "syscall"
yaml "gopkg.in/yaml.v3"
) )
type Fields struct { type Fields struct {
@ -26,19 +28,65 @@ func main() {
ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT) ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
defer can() defer can()
if len(os.Args) < 2 { foo := Main
if err := Recursive(ctx); err != nil { if len(os.Args) == 2 && os.Args[1] == "r" {
panic(err) foo = Recursive
} }
} else { if err := foo(ctx); err != nil {
if err := Main(ctx); err != nil {
panic(err) panic(err)
} }
} }
}
func Recursive(ctx context.Context) error { 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 { func Main(ctx context.Context) error {
@ -54,24 +102,34 @@ func Main(ctx context.Context) error {
var overrides Fields var overrides Fields
json.Unmarshal([]byte(*overridesS), &overrides) json.Unmarshal([]byte(*overridesS), &overrides)
mvNLn := RealMvNLn
if *dry {
mvNLn = DryMvNLn()
}
return Run(ctx, return Run(ctx,
*outd, *outd,
*ind, *ind,
append(flags.Args(), 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>.*) - (?P<episode>[0-9]+).*`,
`^(?P<title>.*) S(?P<season>[0-9]+)E(?P<episode>[0-9]+).*`,
), ),
overrides, overrides,
mvNLn, mvNLn,
) )
} }
func Run(ctx context.Context, outd, ind string, patterns []string, overrides Fields, mvNLn MvNLn) error { func RunWith(ctx context.Context, outd, ind string, patterns []string, overrides Fields, mvNLn MvNLn) error {
entries, err := os.ReadDir(ind) entries, err := readDir(ind)
if err != nil { if err != nil {
return err 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()) return fmt.Errorf("cannot mv_n_ln(%s): (%v) mode=%v", inf, err, stat.Mode())
} }
if _, err := os.Stat(outf); err == nil { 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 { if err := os.Rename(inf, outf); err != nil {
return err return err
@ -155,3 +216,14 @@ func DryMvNLn() func(string, string) error {
return nil 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
}

View File

@ -28,7 +28,7 @@ func TestRunChoosesOne(t *testing.T) {
"Australian_Survivor_S12E12.mkv": false, "Australian_Survivor_S12E12.mkv": false,
} }
if err := main.Run(context.Background(), if err := main.RunWith(context.Background(),
outd, outd,
ind, ind,
[]string{ []string{
@ -53,7 +53,7 @@ func TestRunChoosesOne(t *testing.T) {
} }
} }
func TestRun(t *testing.T) { func TestRunWith(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
given []string given []string
patterns []string patterns []string
@ -106,9 +106,9 @@ func TestRun(t *testing.T) {
} }
outd := t.TempDir() 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) 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) 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
}