diff --git a/cmd/torrent/main.go b/cmd/torrent/main.go index 34219b51..f21bc87e 100644 --- a/cmd/torrent/main.go +++ b/cmd/torrent/main.go @@ -2,132 +2,111 @@ package main import ( - "encoding/json" - "errors" - "fmt" - "io" stdLog "log" "net/http" - "os" - "github.com/anacrolix/args" + "github.com/anacrolix/bargle" "github.com/anacrolix/envpprof" - "github.com/anacrolix/log" xprometheus "github.com/anacrolix/missinggo/v2/prometheus" - "github.com/anacrolix/torrent/bencode" - "github.com/anacrolix/torrent/version" - "github.com/davecgh/go-spew/spew" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) -func main() { - if err := mainErr(); err != nil { - log.Printf("error in main: %v", err) - os.Exit(1) - } -} - func init() { prometheus.MustRegister(xprometheus.NewExpvarCollector()) http.Handle("/metrics", promhttp.Handler()) } -func mainErr() error { - defer envpprof.Stop() - stdLog.SetFlags(stdLog.Flags() | stdLog.Lshortfile) - debug := args.Flag(args.FlagOpt{Long: "debug"}) - return args.ParseMain( - debug, - args.Subcommand("metainfo", metainfoCmd), - args.Subcommand("announce", func(p args.SubCmdCtx) error { - var cmd AnnounceCmd - err := p.NewParser().AddParams( - args.Pos("tracker", &cmd.Tracker), - args.Pos("infohash", &cmd.InfoHash)).Parse() - if err != nil { - return err - } - return announceErr(cmd) - }), - args.Subcommand("scrape", func(p args.SubCmdCtx) error { - var cmd ScrapeCmd - err := p.NewParser().AddParams( - args.Pos("tracker", &cmd.Tracker), - args.Pos("infohash", &cmd.InfoHashes, args.Arity('+'))).Parse() - if err != nil { - return err - } - return scrape(cmd) - }), - args.Subcommand("download", func(p args.SubCmdCtx) error { +func main() { + defer stdLog.SetFlags(stdLog.Flags() | stdLog.Lshortfile) + main := bargle.Main{} + main.Defer(envpprof.Stop) + debug := false + main.Options = append(main.Options, bargle.Flag{Longs: []string{"debug"}, Value: &debug}) + main.Positionals = append(main.Positionals, + bargle.Subcommand{Name: "metainfo", Command: metainfoCmd()}, + //bargle.Subcommand{Name: "announce", Command: func() bargle.Command { + // var cmd AnnounceCmd + // err := p.NewParser().AddParams( + // args.Pos("tracker", &cmd.Tracker), + // args.Pos("infohash", &cmd.InfoHash)).Parse() + // if err != nil { + // return err + // } + // return announceErr(cmd) + //}()}, + //bargle.Subcommand{Name: "scrape", Command: func() bargle.Command { + // var cmd ScrapeCmd + // err := p.NewParser().AddParams( + // args.Pos("tracker", &cmd.Tracker), + // args.Pos("infohash", &cmd.InfoHashes, args.Arity('+'))).Parse() + // if err != nil { + // return err + // } + // return scrape(cmd) + //}()}, + bargle.Subcommand{Name: "download", Command: func() bargle.Command { var dlc DownloadCmd - err := p.NewParser().AddParams( - append(args.FromStruct(&dlc), debug)..., - ).Parse() - if err != nil { - return err - } - dlf := downloadFlags{ - Debug: debug.Bool(), - DownloadCmd: dlc, - } - p.Defer(func() error { - return downloadErr(dlf) - }) - return nil - }), - args.Subcommand( - "bencode", - func(p args.SubCmdCtx) error { - var print func(interface{}) error - if !p.Parse( - args.Subcommand("json", func(ctx args.SubCmdCtx) (err error) { - ctx.Parse() - je := json.NewEncoder(os.Stdout) - je.SetIndent("", " ") - print = je.Encode - return nil - }), - args.Subcommand("spew", func(ctx args.SubCmdCtx) (err error) { - ctx.Parse() - config := spew.NewDefaultConfig() - config.DisableCapacities = true - config.Indent = " " - print = func(v interface{}) error { - config.Dump(v) - return nil - } - return nil - }), - ).RanSubCmd { - return errors.New("an output type is required") - } - d := bencode.NewDecoder(os.Stdin) - p.Defer(func() error { - for i := 0; ; i++ { - var v interface{} - err := d.Decode(&v) - if err == io.EOF { - break - } - if err != nil { - return fmt.Errorf("decoding message index %d: %w", i, err) - } - print(v) - } - return nil + cmd := bargle.FromStruct(&dlc) + cmd.DefaultAction = func() error { + return downloadErr(downloadFlags{ + Debug: debug, + DownloadCmd: dlc, }) - return nil - }, - args.Help("reads bencoding from stdin into Go native types and spews the result"), - ), - args.Subcommand("version", func(p args.SubCmdCtx) error { - fmt.Printf("HTTP User-Agent: %q\n", version.DefaultHttpUserAgent) - fmt.Printf("Torrent client version: %q\n", version.DefaultExtendedHandshakeClientVersion) - fmt.Printf("Torrent version prefix: %q\n", version.DefaultBep20Prefix) - return nil - }), - args.Subcommand("serve", serve, args.Help("creates and seeds a torrent from a filepath")), + } + return cmd + }()}, + //bargle.Subcommand{Name: + // "bencode", Command: func() bargle.Command { + // var print func(interface{}) error + // if !p.Parse( + // args.Subcommand("json", func(ctx args.SubCmdCtx) (err error) { + // ctx.Parse() + // je := json.NewEncoder(os.Stdout) + // je.SetIndent("", " ") + // print = je.Encode + // return nil + // }), + // args.Subcommand("spew", func(ctx args.SubCmdCtx) (err error) { + // ctx.Parse() + // config := spew.NewDefaultConfig() + // config.DisableCapacities = true + // config.Indent = " " + // print = func(v interface{}) error { + // config.Dump(v) + // return nil + // } + // return nil + // }), + // ).RanSubCmd { + // return errors.New("an output type is required") + // } + // d := bencode.NewDecoder(os.Stdin) + // p.Defer(func() error { + // for i := 0; ; i++ { + // var v interface{} + // err := d.Decode(&v) + // if err == io.EOF { + // break + // } + // if err != nil { + // return fmt.Errorf("decoding message index %d: %w", i, err) + // } + // print(v) + // } + // return nil + // }) + // return nil + // }(), + // Desc: "reads bencoding from stdin into Go native types and spews the result", + //}, + //bargle.Subcommand{Name: "version", Command: func() bargle.Command { + // fmt.Printf("HTTP User-Agent: %q\n", version.DefaultHttpUserAgent) + // fmt.Printf("Torrent client version: %q\n", version.DefaultExtendedHandshakeClientVersion) + // fmt.Printf("Torrent version prefix: %q\n", version.DefaultBep20Prefix) + // return nil + //}()}, + bargle.Subcommand{Name: "serve", Command: serve(), Desc: "creates and seeds a torrent from a filepath"}, ) + main.Run() } diff --git a/cmd/torrent/metainfo.go b/cmd/torrent/metainfo.go index f8f558ff..4cca7954 100644 --- a/cmd/torrent/metainfo.go +++ b/cmd/torrent/metainfo.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "github.com/anacrolix/args" + "github.com/anacrolix/bargle" "github.com/anacrolix/torrent/metainfo" "github.com/bradfitz/iter" ) @@ -17,53 +17,64 @@ type pprintMetainfoFlags struct { Files bool } -func metainfoCmd(ctx args.SubCmdCtx) (err error) { +func metainfoCmd() (cmd bargle.Command) { var metainfoPath string var mi *metainfo.MetaInfo - // TODO: Treat no subcommand as a failure. - return ctx.NewParser().AddParams( - args.Pos("torrent file", &metainfoPath, args.AfterParse(func() (err error) { - mi, err = metainfo.LoadFromFile(metainfoPath) - return - })), - args.Subcommand("magnet", func(ctx args.SubCmdCtx) (err error) { - info, err := mi.UnmarshalInfo() - if err != nil { - return - } - fmt.Fprintf(os.Stdout, "%s\n", mi.Magnet(nil, &info).String()) - return nil - }), - args.Subcommand("pprint", func(ctx args.SubCmdCtx) (err error) { - var flags pprintMetainfoFlags - err = ctx.NewParser().AddParams(args.FromStruct(&flags)...).Parse() - if err != nil { - return - } - err = pprintMetainfo(mi, flags) - if err != nil { - return - } - if !flags.JustName { - os.Stdout.WriteString("\n") + // TODO: Test if bargle treats no subcommand as a failure. + cmd.Positionals = append(cmd.Positionals, + &bargle.Positional[*string]{ + Name: "torrent file", + Value: &metainfoPath, + AfterParseFunc: func(ctx bargle.Context) error { + ctx.AfterParse(func() (err error) { + mi, err = metainfo.LoadFromFile(metainfoPath) + return + }) + return nil + }, + }, + bargle.Subcommand{Name: "magnet", Command: func() (cmd bargle.Command) { + cmd.DefaultAction = func() (err error) { + info, err := mi.UnmarshalInfo() + if err != nil { + return + } + fmt.Fprintf(os.Stdout, "%s\n", mi.Magnet(nil, &info).String()) + return nil } return - }), - args.Subcommand("infohash", func(ctx args.SubCmdCtx) (err error) { - fmt.Printf("%s: %s\n", mi.HashInfoBytes().HexString(), metainfoPath) - return nil - }), - args.Subcommand("list-files", func(ctx args.SubCmdCtx) (err error) { - info, err := mi.UnmarshalInfo() - if err != nil { - return fmt.Errorf("unmarshalling info from metainfo at %q: %v", metainfoPath, err) - } - for _, f := range info.UpvertedFiles() { - fmt.Println(f.DisplayPath(&info)) - } - return nil - }), - ).Parse() + }()}, + //bargle.Subcommand{Name: "pprint", Command: func(ctx args.SubCmdCtx) (err error) { + // var flags pprintMetainfoFlags + // err = ctx.NewParser().AddParams(args.FromStruct(&flags)...).Parse() + // if err != nil { + // return + // } + // err = pprintMetainfo(mi, flags) + // if err != nil { + // return + // } + // if !flags.JustName { + // os.Stdout.WriteString("\n") + // } + // return + //}}, + //bargle.Subcommand{Name: "infohash", Command: func(ctx args.SubCmdCtx) (err error) { + // fmt.Printf("%s: %s\n", mi.HashInfoBytes().HexString(), metainfoPath) + // return nil + //}}, + //bargle.Subcommand{Name: "list-files", Command: func(ctx args.SubCmdCtx) (err error) { + // info, err := mi.UnmarshalInfo() + // if err != nil { + // return fmt.Errorf("unmarshalling info from metainfo at %q: %v", metainfoPath, err) + // } + // for _, f := range info.UpvertedFiles() { + // fmt.Println(f.DisplayPath(&info)) + // } + // return nil + //}}, + ) + return } func pprintMetainfo(metainfo *metainfo.MetaInfo, flags pprintMetainfoFlags) error { diff --git a/cmd/torrent/serve.go b/cmd/torrent/serve.go index b32f245b..5b88a529 100644 --- a/cmd/torrent/serve.go +++ b/cmd/torrent/serve.go @@ -5,7 +5,7 @@ import ( "net/http" "path/filepath" - "github.com/anacrolix/args" + "github.com/anacrolix/bargle" "github.com/anacrolix/log" "github.com/anacrolix/torrent" "github.com/anacrolix/torrent/bencode" @@ -13,10 +13,10 @@ import ( "github.com/anacrolix/torrent/storage" ) -func serve(ctx args.SubCmdCtx) error { - var filePath string - ctx.Parse(args.Pos("filePath", &filePath)) - ctx.Defer(func() error { +func serve() (cmd bargle.Command) { + filePath := &bargle.Positional[string]{} + cmd.Positionals = append(cmd.Positionals, filePath) + cmd.DefaultAction = func() error { cfg := torrent.NewDefaultClientConfig() cfg.Seed = true cl, err := torrent.NewClient(cfg) @@ -35,9 +35,9 @@ func serve(ctx args.SubCmdCtx) error { info := metainfo.Info{ PieceLength: pieceLength, } - err = info.BuildFromFilePath(filePath) + err = info.BuildFromFilePath(filePath.Value) if err != nil { - return fmt.Errorf("building info from path %q: %w", filePath, err) + return fmt.Errorf("building info from path %q: %w", filePath.Value, err) } for _, fi := range info.Files { log.Printf("added %q", fi.Path) @@ -54,7 +54,7 @@ func serve(ctx args.SubCmdCtx) error { to, _ := cl.AddTorrentOpt(torrent.AddTorrentOpts{ InfoHash: ih, Storage: storage.NewFileOpts(storage.NewFileClientOpts{ - ClientBaseDir: filePath, + ClientBaseDir: filePath.Value, FilePathMaker: func(opts storage.FilePathMakerOpts) string { return filepath.Join(opts.File.Path...) }, @@ -78,6 +78,6 @@ func serve(ctx args.SubCmdCtx) error { } fmt.Printf("%v: %v\n", to, to.Metainfo().Magnet(&ih, &info)) select {} - }) - return nil + } + return } diff --git a/go.mod b/go.mod index a3313d6b..c49bcf4e 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( require ( github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alexflint/go-scalar v1.1.0 // indirect + github.com/anacrolix/bargle v0.0.0-20220620083758-c3885e1796d1 // indirect github.com/anacrolix/mmsg v1.0.0 // indirect github.com/anacrolix/stm v0.4.0 // indirect github.com/benbjohnson/immutable v0.3.0 // indirect diff --git a/go.sum b/go.sum index 97592e17..8923906d 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4Pnl github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/anacrolix/args v0.5.1-0.20220509024600-c3b77d0b61ac h1:XWoepbk3zgOQ8jMO3vpOnohd6MfENPbFZPivB2L7myc= github.com/anacrolix/args v0.5.1-0.20220509024600-c3b77d0b61ac/go.mod h1:Fj/N2PehEwTBE5t/V/9xgTcxDkuYQ+5IBoFw/8gkldI= +github.com/anacrolix/bargle v0.0.0-20220620083758-c3885e1796d1 h1:RijTNFCxug0EBODz/AmqJDA3Ow700v5jJZQL1j+RLvI= +github.com/anacrolix/bargle v0.0.0-20220620083758-c3885e1796d1/go.mod h1:cC/kX8wL4i1n+63lOrXhPQQlsoxCo0EqV88fGExQwcY= github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= github.com/anacrolix/dht/v2 v2.18.0 h1:btjVjzjKqO5nKGbJHJ2UmuwiRx+EgX3e+OCHC9+WRz8=