test recursive and run as go run ./ r
parent
6af1f231df
commit
daa520de7d
2
go.mod
2
go.mod
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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=
|
||||||
108
main.go
108
main.go
|
|
@ -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(),
|
||||||
`^\[[^\]]*\] (?P<title>.*) - (?P<episode>[0-9]+) .*`,
|
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,
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
102
main_test.go
102
main_test.go
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue