From 8f5f25780e37e23569acb3573dd9dfc9099acfb2 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 13 Oct 2025 13:14:44 -0400 Subject: [PATCH] cmd/hopp-generate: Improve command line interface --- cmd/hopp-generate/main.go | 85 ++++++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/cmd/hopp-generate/main.go b/cmd/hopp-generate/main.go index 78ecb56..01025f7 100644 --- a/cmd/hopp-generate/main.go +++ b/cmd/hopp-generate/main.go @@ -1,48 +1,66 @@ package main import "os" -import "fmt" import "strings" import "path/filepath" +import "git.tebibyte.media/sashakoshka/go-cli" import "git.tebibyte.media/sashakoshka/goparse" import "git.tebibyte.media/sashakoshka/hopp/generate" func main() { - name := os.Args[0] - if len(os.Args) != 3 { - fmt.Fprintf(os.Stderr, "Usage: %s SOURCE DESTINATION\n", name) + flagOutput := cli.NewInputFlag('o', "output", "The output file", "", cli.ValString) + flagPackageName := cli.NewInputFlag('p', "package-name", "The package name of the file", "", cli.ValString) + command := cli.New("Compile PDL files to program source code", + flagOutput, + flagPackageName) + command.Syntax = "FILE [OPTIONS]..." + command.ParseOrExit(os.Args) + + if len(command.Args) != 1 { + command.Usage() os.Exit(2) } - source := os.Args[1] - destination := os.Args[2] + source := command.Args[0] + destination := flagOutput.Value + if destination == "" { + destination = "protocol.go" + } input, err := os.Open(source) - handleErr(1, err) + handleErr(command, 1, err) defer input.Close() protocol, err := generate.ParseReader(source, input) - handleErr(1, err) + handleErr(command, 1, err) - absDestination, err := filepath.Abs(destination) - handleErr(1, err) - packageName := cleanPackageName(strings.ReplaceAll( - strings.ToLower(filepath.Base(absDestination)), - " ", "_")) - destination = filepath.Join(os.Args[2], "generated.go") + packageName := flagPackageName.Value + if packageName == "" { + absDestination, err := filepath.Abs(destination) + handleErr(command, 1, err) + base := filepath.Base(absDestination) + if scrounged, ok := scroungeForPackageName(base); ok { + packageName = scrounged + } else { + packageName = strings.ReplaceAll( + strings.ToLower(base), + " ", "_") + } + } + packageName = cleanPackageName(packageName) output, err := os.Create(destination) - handleErr(1, err) + handleErr(command, 1, err) generator := generate.Generator { Output: output, PackageName: packageName, } _, err = generator.Generate(protocol) - handleErr(1, err) - fmt.Fprintf(os.Stderr, "%s: OK\n", name) + handleErr(command, 1, err) + command.Println(output, "OK") } -func handleErr(code int, err error) { +func handleErr(command *cli.Cli, code int, err error) { if err != nil { - fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], parse.Format(err)) + command.Errorln(parse.Format(err)) os.Exit(code) } } @@ -61,3 +79,32 @@ func cleanPackageName(str string) string { } return string(buffer[:j]) } + +func scroungeForPackageName(dir string) (string, bool) { + entries, err := os.ReadDir(dir) + if err != nil { return "", false} + for _, entry := range entries { + if !entry.Type().IsRegular() { continue } + file, err := os.Open(filepath.Join(dir, entry.Name())) + if err != nil { continue } + defer file.Close() + // FIXME: it is entirely possible that the only file will have + // a shitload of doc comments preceeding the package name, and + // those comments are usually huge so this is bad + buffer := [512]byte { } + n, _ := file.Read(buffer[:]) + text := string(buffer[:n]) + + packageIndex := strings.Index(text, "package") + if packageIndex < 0 { continue } + text = text[packageIndex:] + + newlineIndex := strings.Index(text, "\n") + if packageIndex > 0 { text = text[:newlineIndex] } + + fields := strings.Fields(text) + if len(fields) < 2 { continue } + return fields[1], true + } + return "", false +}