package main import ( "context" "encoding/json" "flag" "fmt" "io" "log/slog" "maps" "net" "net/http" "os" "slices" "golang.org/x/term" "push-f.com/lex-surf/internal/lex" "push-f.com/lex-surf/lex-fetch/at" "push-f.com/lex-surf/lex-fetch/de" "push-f.com/lex-surf/lex-fetch/progress" "push-f.com/lex-surf/lex-fetch/uk" ) type Fetcher interface { Fetch(log *slog.Logger, client *http.Client, reporter *progress.Reporter) ([]lex.Law, error) } var fetchers = map[string]Fetcher{ "at": &at.Fetcher{}, "de": &de.Fetcher{}, "uk": &uk.Fetcher{}, } var logger *slog.Logger func printUsage() { fmt.Printf("usage: %s [options] \n", os.Args[0]) fmt.Printf("where is one of %v\n", slices.Sorted(maps.Keys(fetchers))) fmt.Println("options are:") flag.PrintDefaults() } func main() { debug := flag.Bool("debug", false, "Enable debug logging") flag.Usage = printUsage flag.Parse() args := flag.Args() if len(args) != 2 { printUsage() os.Exit(1) } country := args[0] out := args[1] client := http.Client{ Transport: &CustomTransport{}, } fetcher, ok := fetchers[country] if !ok { printUsage() os.Exit(1) } logOptions := slog.HandlerOptions{} if *debug { logOptions.Level = slog.LevelDebug } logger = slog.New(slog.NewTextHandler(os.Stderr, &logOptions)) var progressReporter progress.Reporter if term.IsTerminal(int(os.Stdout.Fd())) { progressReporter = progress.NewReporter(os.Stdout) } else { progressReporter = progress.NewReporter(io.Discard) } laws, err := fetcher.Fetch(logger, &client, &progressReporter) if err != nil { logger.Error("fetching failed", "error", err) os.Exit(1) } if len(laws) == 0 { logger.Error("fetcher found 0 laws") os.Exit(1) } file, err := os.Create(out) if err != nil { logger.Error("failed to create file", "err", err, "path", out) os.Exit(1) } defer file.Close() err = json.NewEncoder(file).Encode(laws) if err != nil { logger.Error("failed to encode laws as JSON", "err", err) os.Exit(1) } socketPath := os.Getenv("SOCKET_PATH") if socketPath == "" { logger.Info("not notifyng lex-serve because SOCKET_PATH isn't set") } else { client = http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) { return net.Dial("unix", socketPath) }, }, } resp, err := client.Get("http://internal.invalid/update?country=" + country) if err != nil { logger.Error("failed to update lex-serve", "err", err) os.Exit(1) } if resp.StatusCode != 200 { logger.Error("unexpected status code from lex-serve", "statusCode", resp.StatusCode) os.Exit(1) } } } type CustomTransport struct{} func (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) { logger.Debug("request", "method", req.Method, "url", req.URL) req.Header["User-Agent"] = []string{"lex-surf"} return http.DefaultTransport.RoundTrip(req) }