refactor!: split main function into commands BREAKING CHANGE: searchix-web requires `serve` argument
1 file changed, 48 insertions(+), 198 deletions(-)
changed files
M cmd/searchix-web/main.go → cmd/searchix-web/main.go
@@ -1,230 +1,80 @@ package main import ( - "context" "errors" "fmt" "os" - "os/signal" "runtime/pprof" - "sync" - flags "github.com/jessevdk/go-flags" + "alin.ovh/x/log" + "github.com/Southclaws/fault" + "github.com/Southclaws/fault/fmsg" + "github.com/jessevdk/go-flags" - "alin.ovh/searchix/frontend" - "alin.ovh/searchix/internal/components" "alin.ovh/searchix/internal/config" - "alin.ovh/searchix/internal/file" - "alin.ovh/searchix/internal/importer" - "alin.ovh/searchix/internal/index" - "alin.ovh/searchix/internal/manpages" - "alin.ovh/searchix/internal/server" - "alin.ovh/searchix/internal/storage" - "alin.ovh/searchix/web" - "alin.ovh/x/log" ) -var options struct { - Config flags.Filename `long:"config" default:"config.toml" description:"config file to use"` - PrintDefaultConfig bool `long:"print-default-config" description:"print default configuration and exit"` - GenerateErrorPage bool `long:"generate-error-page" description:"generate error page and exit"` - Dev bool `long:"dev" description:"enable live reloading and nicer logging"` - Prefetch bool `long:"prefetch" description:"pre-fetch data and exit"` - Replace bool `long:"replace" description:"replace existing storage and exit"` - Reindex bool `long:"reindex" description:"reindex existing index and exit"` - Rebuild bool `long:"rebuild" description:"rebuild existing index and exit"` - Offline bool `long:"offline" description:"run in offline mode"` - Version bool `long:"version" description:"print version information"` - CPUProfile flags.Filename `long:"cpuprofile" description:"output CPU profile to FILE" value-name:"FILE"` +type Options struct { + Dev bool `long:"dev" description:"enable live reloading and nicer logging"` + Config flags.Filename `long:"config" description:"config file to use"` + CPUProfile flags.Filename `long:"cpuprofile" description:"output CPU profile to FILE" value-name:"FILE"` } -var parser = flags.NewParser(&options, flags.Default) - -func main() { - _, err := parser.Parse() - if err != nil { - os.Exit(1) - } - if flags.WroteHelp(err) { - return - } - - if options.Version { - _, err := fmt.Fprintf(os.Stderr, "searchix %s\n", config.Version) - if err != nil { - panic("can't write to standard error?!") - } - os.Exit(0) - } - if options.PrintDefaultConfig { - _, err := fmt.Print(config.GetDefaultConfig()) - if err != nil { - panic("can't write to standard output?!") - } - os.Exit(0) - } - if options.GenerateErrorPage { - assets, err := frontend.New() - if err != nil { - panic("failed to create assets: " + err.Error()) - } - - err = components.ErrorTemplate(components.TemplateData{ - Source: nil, - Sources: []*config.Source{}, - Query: "", - ExtraHeadHTML: "", - Code: 0, - Message: `{{placeholder "http.error.status_code"}} {{placeholder "http.error.status_text"}}`, - Assets: assets, - }).Render(os.Stdout) - if err != nil { - panic("failed to render error template: " + err.Error()) - } - os.Exit(0) - } - - if options.CPUProfile != "" { - //nolint:forbidigo // admin specifies profile file location - f, err := os.Create(string(options.CPUProfile)) - if err != nil { - panic("can't create CPU profile: " + err.Error()) - } - err = pprof.StartCPUProfile(f) - if err != nil { - panic("can't start CPU profile: " + err.Error()) - } - defer pprof.StopCPUProfile() - } - - logger := log.Configure(!options.Dev) - - cfg, err := config.GetConfig(string(options.Config), logger) - if err != nil { - logger.Fatal("Failed to parse config file", "error", err) - } - - log.SetLevel(cfg.LogLevel) - - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) - defer cancel() - - root, err := file.CreateAndOpenRoot(cfg.DataPath) - if err != nil { - logger.Fatal("Failed to open data root", "error", err) - } - defer root.Close() - - store, err := storage.New(&storage.Options{ - Root: root, - Logger: logger.Named("store"), - }) - if err != nil { - logger.Fatal("Failed to create store", "error", err) - } - defer store.Close() - - read, write, exists, err := index.OpenOrCreate( - &index.Options{ - Config: cfg, - Force: options.Reindex, - LowMemory: cfg.Importer.LowMemory, - BatchSize: cfg.Importer.BatchSize, - Logger: logger.Named("index"), - Root: root, - Store: store, - }, - ) - if err != nil { - logger.Fatal("Failed to open or create index", "error", err) - } +var ( + globalOptions Options + cfg *config.Config + logger *log.Logger +) - mdb := manpages.New(&manpages.Options{ - Logger: logger.Named("manpages"), - Root: root, - }) +var parser = flags.NewParser(&globalOptions, flags.HelpFlag|flags.PassDoubleDash) - s, err := web.New(cfg, logger, &server.Options{ - ReadIndex: read, - ManpagesURLMap: mdb, - Store: store, - }) - if err != nil { - logger.Fatal("Failed to initialise searchix-web", "error", err) - } +func main() { + parser.CommandHandler = func(cmd flags.Commander, args []string) (err error) { + switch cmd.(type) { + case *PrintDefaultsOptions, *Version: + default: + logger = log.Configure(!globalOptions.Dev) - imp, err := importer.New(cfg, &importer.Options{ - Storage: store, - WriteIndex: write, - LowMemory: cfg.Importer.LowMemory, - Logger: logger.Named("importer"), - Manpages: mdb, - Root: root, - Offline: options.Offline, - }) - if err != nil { - logger.Fatal("Failed to create importer", "error", err) - } + cfg, err = config.GetConfig(string(globalOptions.Config), logger.Named("config")) + if err != nil { + return fault.Wrap(err, fmsg.With("Failed to parse config file")) + } - if !exists || options.Replace || options.Prefetch { - err := imp.Start(ctx, true, options.Prefetch, nil) - if err != nil { - logger.Fatal("Failed to start importer", "error", err) + log.SetLevel(cfg.LogLevel) } - if options.Replace || options.Prefetch { - return - } - } - - if !exists || options.Reindex { - for _, source := range cfg.Importer.Sources { - hadErrors, err := importer.ImportSource( - ctx, - logger.Named("importer"), - store, - source, - write, - ) + if globalOptions.CPUProfile != "" { + //nolint:forbidigo // admin specifies profile file location + f, err := os.Create(string(globalOptions.CPUProfile)) if err != nil { - logger.Fatal("Failed to import source", "source", source.Name, "error", err) + panic("can't create CPU profile: " + err.Error()) } - - if hadErrors { - logger.Warn("Imported source encountered errors", "source", source.Name) + err = pprof.StartCPUProfile(f) + if err != nil { + panic("can't start CPU profile: " + err.Error()) } + defer pprof.StopCPUProfile() } - if options.Reindex { - return - } + return cmd.Execute(args) } - err = imp.EnsureSourcesIndexed(ctx, read) + _, err := parser.Parse() if err != nil { - logger.Fatal("Failed to setup index", "error", err) - } + var flagErr *flags.Error + if errors.As(err, &flagErr) { + switch flagErr.Type { + case flags.ErrHelp: + parser.WriteHelp(os.Stdout) - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - sCtx, cancel := context.WithCancel(ctx) - defer wg.Done() - defer cancel() - err := s.Start(sCtx, options.Dev) - if err != nil && !errors.Is(err, context.Canceled) { - // Error starting or closing listener: - logger.Fatal("error", "error", err) + return + default: + fmt.Println(flagErr.Error()) + } + } else { + fmt.Println(err) } - }() - wg.Add(1) - go func() { - defer wg.Done() - imp.StartUpdateTimer(ctx) - }() - - <-ctx.Done() - s.Stop() - wg.Wait() + os.Exit(1) + } }