From b27f5b8e3eaccdf1c94d214a8bdfdc8ded4399d9 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 10 Feb 2026 16:55:24 +0100 Subject: [PATCH 1/8] feat(cli): panic instead of logging after a fatal error --- polybase/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/polybase/main.go b/polybase/main.go index 2636902..044cce5 100644 --- a/polybase/main.go +++ b/polybase/main.go @@ -19,17 +19,17 @@ func main() { func run() error { dbPath, args, err := parseArgs() if err != nil { - return err + panic(err) } db, err := sql.Open("sqlite", dbPath) if err != nil { - return fmt.Errorf("failed to open database: %w", err) + panic(err) } defer db.Close() if err := db.Ping(); err != nil { - return fmt.Errorf("invalid database file: %w", err) + panic(err) } return dispatch(libpolybase.New(db, "/var/log/polybase/polybase.log", false), args) From db0fecbdf3ab24c969ef4d351fa41d735b58efd6 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Thu, 12 Feb 2026 09:19:56 +0100 Subject: [PATCH 2/8] refactor(cli): use flag package and simplify arg parsing --- polybase/args.go | 90 ----------------------------- polybase/commands.go | 133 +++++++++++++++++++------------------------ polybase/main.go | 91 +++++++++++++++++++++++++---- polybase/print.go | 9 ++- 4 files changed, 144 insertions(+), 179 deletions(-) delete mode 100644 polybase/args.go diff --git a/polybase/args.go b/polybase/args.go deleted file mode 100644 index e21b288..0000000 --- a/polybase/args.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "io" - "os" - - "github.com/alias-asso/polybase-go/libpolybase" -) - -const defaultDBPath = "/var/lib/polybase/polybase.db" -const version = "0.1.0" - -func parseArgs() (string, []string, error) { - flags := flag.NewFlagSet("polybase", flag.ContinueOnError) - flags.SetOutput(io.Discard) - flags.Usage = func() {} - - for i, arg := range os.Args[1:] { - if arg == "-h" || arg == "help" { - if err := flags.Parse(os.Args[i+2:]); err != nil { - printUsage() - return "", nil, err - } - - args := flags.Args() - - if err := runHelp(args); err != nil { - return "", nil, err - } - os.Exit(0) - } - } - - for _, arg := range os.Args[1:] { - if arg == "-v" || arg == "version" { - fmt.Printf("polybase version %s\n", version) - os.Exit(0) - } - } - - dbPath := flags.String("db", defaultDBPath, "Database path") - - if err := flags.Parse(os.Args[1:]); err != nil { - printUsage() - return "", nil, err - } - - args := flags.Args() - if len(args) == 0 { - printUsage() - return "", nil, nil - } - - return *dbPath, args, nil -} - -func dispatch(pb libpolybase.Polybase, args []string) error { - if len(args) == 0 { - return fmt.Errorf("no command specified") - } - - ctx := context.Background() - cmd := args[0] - cmdArgs := args[1:] - - switch cmd { - case "create": - return runCreate(pb, ctx, cmdArgs) - case "get": - return runGet(pb, ctx, cmdArgs) - case "update": - return runUpdate(pb, ctx, cmdArgs) - case "delete": - return runDelete(pb, ctx, cmdArgs) - case "list": - return runList(pb, ctx, cmdArgs) - case "quantity": - return runQuantity(pb, ctx, cmdArgs) - case "visibility": - return runVisibility(pb, ctx, cmdArgs) - case "help": - return runHelp(cmdArgs) - default: - printUsage() - return fmt.Errorf("unknown command: %s", cmd) - } -} diff --git a/polybase/commands.go b/polybase/commands.go index 78666b7..28ee32d 100644 --- a/polybase/commands.go +++ b/polybase/commands.go @@ -2,9 +2,9 @@ package main import ( "context" + "errors" "flag" "fmt" - "io" "os" "os/user" "strconv" @@ -15,15 +15,29 @@ import ( "github.com/alias-asso/polybase-go/libpolybase" ) -func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) error { +var ( + ErrInvalidUsage = errors.New("invalid usage") +) + +func scope(args []string, usage func()) ([]string, string, string, uint8, error) { if len(args) < 3 { - printCreateUsage() - return fmt.Errorf("CODE, KIND and PART are required") + usage() + return nil, "", "", 0, errors.Join(ErrInvalidUsage, errors.New("CODE, KIND and PART are required")) + } + part, err := strconv.Atoi(args[2]) + if err != nil || part < 0 || part > 255 { + return nil, "", "", 0, errors.Join(ErrInvalidUsage, fmt.Errorf("invalid part number: %s", args[2])) + } + return args[3:], args[0], args[1], uint8(part), nil +} + +func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) error { + args, code, kind, part, err := scope(args, printCreateUsage) + if err != nil { + return err } - flags := flag.NewFlagSet("create", flag.ContinueOnError) - flags.SetOutput(io.Discard) - flags.Usage = func() {} + flags := flag.NewFlagSet("create", flag.PanicOnError) name := flags.String("n", "", "course name") quantity := flags.Int("q", -1, "initial quantity") @@ -31,38 +45,30 @@ func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) erro semester := flags.String("s", "", "semester") jsonOutput := flags.Bool("json", false, "output in JSON format") - if err := flags.Parse(args[3:]); err != nil { + if err := flags.Parse(args); err != nil { return err } if *name == "" || *quantity == -1 || *semester == "" { printCreateUsage() - return fmt.Errorf("name (-n), quantity (-q) and semester (-s) are required") - } - - part, err := strconv.Atoi(args[2]) - if err != nil { - return fmt.Errorf("invalid part number: %s", args[2]) + return errors.Join(ErrInvalidUsage, fmt.Errorf("name (-n), quantity (-q) and semester (-s) are required")) } if *total == 0 { *total = *quantity } - course := libpolybase.Course{ - Code: args[0], - Kind: args[1], - Part: part, + created, err := pb.CreateCourse(ctx, getCurrentUser(), libpolybase.Course{ + Code: code, + Kind: kind, + Part: int(part), Parts: 0, Name: *name, Quantity: *quantity, Total: *total, Shown: true, Semester: *semester, - } - - username := getCurrentUser() - created, err := pb.CreateCourse(ctx, username, course) + }) if err != nil { return err } @@ -71,27 +77,22 @@ func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) erro } func runGet(pb libpolybase.Polybase, ctx context.Context, args []string) error { - if len(args) < 3 { - printGetUsage() - return fmt.Errorf("CODE, KIND and PART are required") - } - - part, err := strconv.Atoi(args[2]) + args, code, kind, part, err := scope(args, printCreateUsage) if err != nil { - return fmt.Errorf("invalid part number: %s", args[2]) + return err } flags := flag.NewFlagSet("get", flag.ContinueOnError) jsonOutput := flags.Bool("json", false, "output in JSON format") - if err := flags.Parse(args[3:]); err != nil { + if err := flags.Parse(args); err != nil { return err } id := libpolybase.CourseID{ - Code: args[0], - Kind: args[1], - Part: part, + Code: code, + Kind: kind, + Part: int(part), } course, err := pb.GetCourse(ctx, id) @@ -103,22 +104,15 @@ func runGet(pb libpolybase.Polybase, ctx context.Context, args []string) error { } func runUpdate(pb libpolybase.Polybase, ctx context.Context, args []string) error { - if len(args) < 3 { - printUpdateUsage() - return fmt.Errorf("CODE, KIND and PART are required") - } - - code := args[0] - kind := args[1] - part, err := strconv.Atoi(args[2]) + args, code, kind, part, err := scope(args, printCreateUsage) if err != nil { - return fmt.Errorf("invalid part number: %s", args[2]) + return err } id := libpolybase.CourseID{ Code: code, Kind: kind, - Part: part, + Part: int(part), } flags := flag.NewFlagSet("update", flag.ContinueOnError) @@ -131,7 +125,7 @@ func runUpdate(pb libpolybase.Polybase, ctx context.Context, args []string) erro newSemester := flags.String("s", "", "update semester") jsonOutput := flags.Bool("json", false, "output in JSON format") - if err := flags.Parse(args[3:]); err != nil { + if err := flags.Parse(args); err != nil { return err } @@ -165,20 +159,15 @@ func runUpdate(pb libpolybase.Polybase, ctx context.Context, args []string) erro } func runDelete(pb libpolybase.Polybase, ctx context.Context, args []string) error { - if len(args) < 3 { - printDeleteUsage() - return fmt.Errorf("CODE, KIND and PART are required") - } - - part, err := strconv.Atoi(args[2]) + args, code, kind, part, err := scope(args, printCreateUsage) if err != nil { - return fmt.Errorf("invalid part number: %s", args[2]) + return err } id := libpolybase.CourseID{ - Code: args[0], - Kind: args[1], - Part: part, + Code: code, + Kind: kind, + Part: int(part), } course, err := pb.GetCourse(ctx, id) @@ -255,28 +244,27 @@ func runQuantity(pb libpolybase.Polybase, ctx context.Context, args []string) er printQuantityUsage() return fmt.Errorf("CODE, KIND, PART and DELTA are required") } - - part, err := strconv.Atoi(args[2]) + args, code, kind, part, err := scope(args, printCreateUsage) if err != nil { - return fmt.Errorf("invalid part number: %s", args[2]) + return err } - delta, err := strconv.Atoi(args[3]) + delta, err := strconv.Atoi(args[0]) if err != nil { - return fmt.Errorf("invalid delta value: %s", args[3]) + return fmt.Errorf("invalid delta value: %s", args[0]) } flags := flag.NewFlagSet("get", flag.ContinueOnError) jsonOutput := flags.Bool("json", false, "output in JSON format") - if err := flags.Parse(args[4:]); err != nil { + if err := flags.Parse(args[1:]); err != nil { return err } id := libpolybase.CourseID{ - Code: args[0], - Kind: args[1], - Part: part, + Code: code, + Kind: kind, + Part: int(part), } username := getCurrentUser() @@ -289,28 +277,23 @@ func runQuantity(pb libpolybase.Polybase, ctx context.Context, args []string) er } func runVisibility(pb libpolybase.Polybase, ctx context.Context, args []string) error { - if len(args) < 3 { - printVisibilityUsage() - return fmt.Errorf("CODE, KIND and PART are required") + args, code, kind, part, err := scope(args, printCreateUsage) + if err != nil { + return err } flags := flag.NewFlagSet("visibility", flag.ContinueOnError) shown := flags.Bool("s", true, "visibility state") jsonOutput := flags.Bool("json", false, "output in JSON format") - if err := flags.Parse(args[3:]); err != nil { + if err := flags.Parse(args); err != nil { return err } - part, err := strconv.Atoi(args[2]) - if err != nil { - return fmt.Errorf("invalid part number: %s", args[2]) - } - id := libpolybase.CourseID{ - Code: args[0], - Kind: args[1], - Part: part, + Code: code, + Kind: kind, + Part: int(part), } username := getCurrentUser() diff --git a/polybase/main.go b/polybase/main.go index 044cce5..cb1508f 100644 --- a/polybase/main.go +++ b/polybase/main.go @@ -1,25 +1,54 @@ package main import ( + "context" "database/sql" + "errors" + "flag" "fmt" - "os" "github.com/alias-asso/polybase-go/libpolybase" _ "modernc.org/sqlite" ) +const ( + version = "0.1.0" + defaultDBPath = "/var/lib/polybase/polybase.db" +) + +// Global args +var ( + showHelp = false + showVersion = false + dbPath = defaultDBPath +) + +func init() { + flag.BoolVar(&showHelp, "h", showHelp, "display the help") + flag.BoolVar(&showHelp, "help", showHelp, "display the help") + flag.BoolVar(&showVersion, "v", showVersion, "display the version of polybase") + flag.StringVar(&dbPath, "db", dbPath, "path of the database") +} + func main() { - if err := run(); err != nil { - fmt.Fprintf(os.Stderr, "\nerror: %v\n", err) - os.Exit(1) + flag.Parse() + if showHelp { + printUsage() + return + } + if showVersion { + printVersion() + return } -} -func run() error { - dbPath, args, err := parseArgs() - if err != nil { - panic(err) + args := flag.Args() + if len(args) == 0 { + printUsage() + return + } + if args[0] == "version" { + printVersion() + return } db, err := sql.Open("sqlite", dbPath) @@ -28,9 +57,49 @@ func run() error { } defer db.Close() - if err := db.Ping(); err != nil { + if err = db.Ping(); err != nil { panic(err) } - return dispatch(libpolybase.New(db, "/var/log/polybase/polybase.log", false), args) + err = dispatch(libpolybase.New(db, "/var/log/polybase/polybase.log", false), flag.Args()) + if err != nil { + panic(err) + } +} + +var ( + ErrNoCommand = errors.New("no command specified") + ErrUnknownCommand = errors.New("unknown command") +) + +func dispatch(pb libpolybase.Polybase, args []string) error { + if len(args) == 0 { + return ErrNoCommand + } + + ctx := context.Background() + cmd := args[0] + cmdArgs := args[1:] + + switch cmd { + case "create": + return runCreate(pb, ctx, cmdArgs) + case "get": + return runGet(pb, ctx, cmdArgs) + case "update": + return runUpdate(pb, ctx, cmdArgs) + case "delete": + return runDelete(pb, ctx, cmdArgs) + case "list": + return runList(pb, ctx, cmdArgs) + case "quantity": + return runQuantity(pb, ctx, cmdArgs) + case "visibility": + return runVisibility(pb, ctx, cmdArgs) + case "help": + return runHelp(cmdArgs) + default: + printUsage() + return errors.Join(ErrUnknownCommand, fmt.Errorf("command %s not supported", cmd)) + } } diff --git a/polybase/print.go b/polybase/print.go index b188d61..8dbb050 100644 --- a/polybase/print.go +++ b/polybase/print.go @@ -31,6 +31,10 @@ Use "polybase help command" for more information about a command. `, defaultDBPath) } +func printVersion() { + fmt.Printf("polybase version %s\n", version) +} + func printCreateUsage() { fmt.Print(`Usage: polybase create [OPTIONS] Create a new course entry. @@ -153,7 +157,7 @@ func printCourses(courses []libpolybase.Course, jsonOutput bool) error { func printCourse(c libpolybase.Course, jsonOutput bool) error { if jsonOutput { - courseJSON := CourseJSON{ + return json.NewEncoder(os.Stdout).Encode(CourseJSON{ Code: c.Code, Kind: c.Kind, Part: c.Part, @@ -163,8 +167,7 @@ func printCourse(c libpolybase.Course, jsonOutput bool) error { Total: c.Total, Shown: c.Shown, Semester: c.Semester, - } - return json.NewEncoder(os.Stdout).Encode(courseJSON) + }) } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) From 400f353e04b8be7df7efcf2f4c96daaf5a92df59 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Thu, 12 Feb 2026 09:25:58 +0100 Subject: [PATCH 3/8] refactor(cli): generalize course json creation --- polybase/print.go | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/polybase/print.go b/polybase/print.go index 8dbb050..6d03b47 100644 --- a/polybase/print.go +++ b/polybase/print.go @@ -124,21 +124,25 @@ type CourseJSON struct { Semester string `json:"semester"` } +func newCourseJSON(c *libpolybase.Course) CourseJSON { + return CourseJSON{ + Code: c.Code, + Kind: c.Kind, + Part: c.Part, + Parts: c.Parts, + Name: c.Name, + Quantity: c.Quantity, + Total: c.Total, + Shown: c.Shown, + Semester: c.Semester, + } +} + func printCourses(courses []libpolybase.Course, jsonOutput bool) error { if jsonOutput { var coursesJSON []CourseJSON for _, c := range courses { - coursesJSON = append(coursesJSON, CourseJSON{ - Code: c.Code, - Kind: c.Kind, - Part: c.Part, - Parts: c.Parts, - Name: c.Name, - Quantity: c.Quantity, - Total: c.Total, - Shown: c.Shown, - Semester: c.Semester, - }) + coursesJSON = append(coursesJSON, newCourseJSON(&c)) } return json.NewEncoder(os.Stdout).Encode(courses) @@ -157,17 +161,7 @@ func printCourses(courses []libpolybase.Course, jsonOutput bool) error { func printCourse(c libpolybase.Course, jsonOutput bool) error { if jsonOutput { - return json.NewEncoder(os.Stdout).Encode(CourseJSON{ - Code: c.Code, - Kind: c.Kind, - Part: c.Part, - Parts: c.Parts, - Name: c.Name, - Quantity: c.Quantity, - Total: c.Total, - Shown: c.Shown, - Semester: c.Semester, - }) + return json.NewEncoder(os.Stdout).Encode(newCourseJSON(&c)) } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) From a803b6bb27f18bc60b0306106e2e3018c50dddcd Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Thu, 12 Feb 2026 11:02:26 +0100 Subject: [PATCH 4/8] refactor(cli): generate automatically options based on flags --- polybase/commands.go | 109 ++++++++++++++++--------------------- polybase/main.go | 2 - polybase/print.go | 125 ++++++++++++++++++++----------------------- 3 files changed, 105 insertions(+), 131 deletions(-) diff --git a/polybase/commands.go b/polybase/commands.go index 28ee32d..4259d17 100644 --- a/polybase/commands.go +++ b/polybase/commands.go @@ -32,12 +32,8 @@ func scope(args []string, usage func()) ([]string, string, string, uint8, error) } func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) error { - args, code, kind, part, err := scope(args, printCreateUsage) - if err != nil { - return err - } - - flags := flag.NewFlagSet("create", flag.PanicOnError) + flags := flag.NewFlagSet("create", flag.ExitOnError) + flags.Usage = createUsage(flags) name := flags.String("n", "", "course name") quantity := flags.Int("q", -1, "initial quantity") @@ -45,12 +41,17 @@ func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) erro semester := flags.String("s", "", "semester") jsonOutput := flags.Bool("json", false, "output in JSON format") + args, code, kind, part, err := scope(args, flags.Usage) + if err != nil { + return err + } + if err := flags.Parse(args); err != nil { return err } if *name == "" || *quantity == -1 || *semester == "" { - printCreateUsage() + createUsage(flags) return errors.Join(ErrInvalidUsage, fmt.Errorf("name (-n), quantity (-q) and semester (-s) are required")) } @@ -62,7 +63,6 @@ func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) erro Code: code, Kind: kind, Part: int(part), - Parts: 0, Name: *name, Quantity: *quantity, Total: *total, @@ -77,14 +77,16 @@ func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) erro } func runGet(pb libpolybase.Polybase, ctx context.Context, args []string) error { - args, code, kind, part, err := scope(args, printCreateUsage) + flags := flag.NewFlagSet("get", flag.ExitOnError) + flags.Usage = getUsage(flags) + + jsonOutput := flags.Bool("json", false, "output in JSON format") + + args, code, kind, part, err := scope(args, flags.Usage) if err != nil { return err } - flags := flag.NewFlagSet("get", flag.ContinueOnError) - jsonOutput := flags.Bool("json", false, "output in JSON format") - if err := flags.Parse(args); err != nil { return err } @@ -104,18 +106,9 @@ func runGet(pb libpolybase.Polybase, ctx context.Context, args []string) error { } func runUpdate(pb libpolybase.Polybase, ctx context.Context, args []string) error { - args, code, kind, part, err := scope(args, printCreateUsage) - if err != nil { - return err - } + flags := flag.NewFlagSet("update", flag.ExitOnError) + flags.Usage = updateUsage(flags) - id := libpolybase.CourseID{ - Code: code, - Kind: kind, - Part: int(part), - } - - flags := flag.NewFlagSet("update", flag.ContinueOnError) newCode := flags.String("c", "", "update code") newKind := flags.String("k", "", "update kind") newPart := flags.Int("p", 0, "update part") @@ -125,6 +118,17 @@ func runUpdate(pb libpolybase.Polybase, ctx context.Context, args []string) erro newSemester := flags.String("s", "", "update semester") jsonOutput := flags.Bool("json", false, "output in JSON format") + args, code, kind, part, err := scope(args, flags.Usage) + if err != nil { + return err + } + + id := libpolybase.CourseID{ + Code: code, + Kind: kind, + Part: int(part), + } + if err := flags.Parse(args); err != nil { return err } @@ -159,7 +163,7 @@ func runUpdate(pb libpolybase.Polybase, ctx context.Context, args []string) erro } func runDelete(pb libpolybase.Polybase, ctx context.Context, args []string) error { - args, code, kind, part, err := scope(args, printCreateUsage) + args, code, kind, part, err := scope(args, deleteUsage(nil)) if err != nil { return err } @@ -204,7 +208,9 @@ func runDelete(pb libpolybase.Polybase, ctx context.Context, args []string) erro } func runList(pb libpolybase.Polybase, ctx context.Context, args []string) error { - flags := flag.NewFlagSet("list", flag.ContinueOnError) + flags := flag.NewFlagSet("list", flag.ExitOnError) + flags.Usage = listUsage(flags) + showHidden := flags.Bool("a", false, "show hidden courses") semester := flags.String("s", "", "filter by semester") code := flags.String("c", "", "filter by course code") @@ -240,11 +246,17 @@ func runList(pb libpolybase.Polybase, ctx context.Context, args []string) error } func runQuantity(pb libpolybase.Polybase, ctx context.Context, args []string) error { + flags := flag.NewFlagSet("get", flag.ExitOnError) + flags.Usage = quantityUsage(flags) + + jsonOutput := flags.Bool("json", false, "output in JSON format") + if len(args) < 4 { - printQuantityUsage() + flags.Usage() return fmt.Errorf("CODE, KIND, PART and DELTA are required") } - args, code, kind, part, err := scope(args, printCreateUsage) + + args, code, kind, part, err := scope(args, flags.Usage) if err != nil { return err } @@ -254,9 +266,6 @@ func runQuantity(pb libpolybase.Polybase, ctx context.Context, args []string) er return fmt.Errorf("invalid delta value: %s", args[0]) } - flags := flag.NewFlagSet("get", flag.ContinueOnError) - jsonOutput := flags.Bool("json", false, "output in JSON format") - if err := flags.Parse(args[1:]); err != nil { return err } @@ -277,15 +286,17 @@ func runQuantity(pb libpolybase.Polybase, ctx context.Context, args []string) er } func runVisibility(pb libpolybase.Polybase, ctx context.Context, args []string) error { - args, code, kind, part, err := scope(args, printCreateUsage) - if err != nil { - return err - } + flags := flag.NewFlagSet("visibility", flag.ExitOnError) + flags.Usage = visibilityUsage(flags) - flags := flag.NewFlagSet("visibility", flag.ContinueOnError) shown := flags.Bool("s", true, "visibility state") jsonOutput := flags.Bool("json", false, "output in JSON format") + args, code, kind, part, err := scope(args, flags.Usage) + if err != nil { + return err + } + if err := flags.Parse(args); err != nil { return err } @@ -305,34 +316,6 @@ func runVisibility(pb libpolybase.Polybase, ctx context.Context, args []string) return printCourse(updated, *jsonOutput) } -func runHelp(args []string) error { - if len(args) == 0 { - printUsage() - return nil - } - - switch args[0] { - case "create": - printCreateUsage() - case "get": - printGetUsage() - case "update": - printUpdateUsage() - case "delete": - printDeleteUsage() - case "list": - printListUsage() - case "quantity": - printQuantityUsage() - case "visibility": - printVisibilityUsage() - default: - printUsage() - return fmt.Errorf("unknown command %q", args[0]) - } - return nil -} - func getCurrentUser() string { currentUser, err := user.Current() if err != nil { diff --git a/polybase/main.go b/polybase/main.go index cb1508f..46635ea 100644 --- a/polybase/main.go +++ b/polybase/main.go @@ -96,8 +96,6 @@ func dispatch(pb libpolybase.Polybase, args []string) error { return runQuantity(pb, ctx, cmdArgs) case "visibility": return runVisibility(pb, ctx, cmdArgs) - case "help": - return runHelp(cmdArgs) default: printUsage() return errors.Join(ErrUnknownCommand, fmt.Errorf("command %s not supported", cmd)) diff --git a/polybase/print.go b/polybase/print.go index 6d03b47..a5f74dd 100644 --- a/polybase/print.go +++ b/polybase/print.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "flag" "fmt" "os" "text/tabwriter" @@ -25,9 +26,6 @@ COMMANDS list List all courses quantity Update course quantity visibility Set course visibility - help Show help message for a specific command - -Use "polybase help command" for more information about a command. `, defaultDBPath) } @@ -35,81 +33,77 @@ func printVersion() { fmt.Printf("polybase version %s\n", version) } -func printCreateUsage() { - fmt.Print(`Usage: polybase create [OPTIONS] - Create a new course entry. - - Options: - -n NAME Course name (required) - -q QUANTITY Initial quantity (required) - -t TOTAL Total quantity (default: same as quantity) - -s SEMESTER Semester (required) - -json Output in JSON format -`) +func usage(usage, target string, flags *flag.FlagSet) func() { + return func() { + fmt.Printf("Usage: %s\n\t%s\n\n", usage, target) + if flags != nil { + fmt.Println("Options:") + flags.VisitAll(func(f *flag.Flag) { + def := f.DefValue + if len(def) == 0 { + def = `""` + } + fmt.Printf("-%s\t%s (default: %v)\n", f.Name, f.Usage, def) + }) + fmt.Println() + } + } } -func printGetUsage() { - fmt.Print(`Usage: polybase get [OPTIONS] - Display details for a specific course - - Options: - -json Output in JSON format -`) +func createUsage(flags *flag.FlagSet) func() { + return usage( + `polybase create [OPTIONS]`, + `Create a new course entry.`, + flags, + ) } -func printUpdateUsage() { - fmt.Print(`Usage: polybase update [OPTIONS] - Update course information - - Options: - -c CODE Update course code - -k KEY Update course key - -p PART Update course part - -n NAME Update course name - -q QUANTITY Update quantity - -t TOTAL Update total quantity - -s SEMESTER Update semester - -json Output in JSON format -`) +func getUsage(flags *flag.FlagSet) func() { + return usage( + `polybase get [OPTIONS]`, + `Display details for a specific course`, + flags, + ) } -func printDeleteUsage() { - fmt.Print(`Usage: polybase delete - Remove a course from the database -`) +func updateUsage(flags *flag.FlagSet) func() { + return usage( + `polybase update [OPTIONS]`, + `Update course information`, + flags, + ) } -func printListUsage() { - fmt.Print(`Usage: polybase list [OPTIONS] - List all courses - - Options: - -a Show hidden courses - -s SEMESTER Filter by semester - -c CODE Filter by code prefix - -k KIND Filter by kind - -p PART Filter by part number - -json Output in JSON format -`) +func deleteUsage(flags *flag.FlagSet) func() { + return usage( + `polybase delete `, + `Remove a course from the database`, + flags, + ) } -func printQuantityUsage() { - fmt.Print(`Usage: polybase quantity [OPTIONS] - Update course quantity by adding DELTA (can be negative) - - Options: - -json Output in JSON format -`) +func listUsage(flags *flag.FlagSet) func() { + return usage( + `polybase list [OPTIONS]`, + `List all courses`, + flags, + ) } -func printVisibilityUsage() { - fmt.Print(`Usage: polybase visibility [-s STATE] [OPTIONS] - Set course visibility +func quantityUsage(flags *flag.FlagSet) func() { + return usage( + `polybase quantity [OPTIONS]`, + `Update course quantity by adding DELTA (can be negative)`, + flags, + ) +} - Options: - -s Set visibility state (default: true) - -json Output in JSON format -`) +func visibilityUsage(flags *flag.FlagSet) func() { + return usage( + `polybase visibility [-s STATE] [OPTIONS]`, + `Set course visibility`, + flags, + ) } type CourseJSON struct { @@ -144,7 +138,6 @@ func printCourses(courses []libpolybase.Course, jsonOutput bool) error { for _, c := range courses { coursesJSON = append(coursesJSON, newCourseJSON(&c)) } - return json.NewEncoder(os.Stdout).Encode(courses) } From 765b4ffe6ff9ccc4429e94581431ce654141324d Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Thu, 12 Feb 2026 11:07:17 +0100 Subject: [PATCH 5/8] fix(cli): missing case handling in update --- polybase/commands.go | 18 ++---------------- polybase/main.go | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/polybase/commands.go b/polybase/commands.go index 4259d17..ceeb703 100644 --- a/polybase/commands.go +++ b/polybase/commands.go @@ -6,9 +6,7 @@ import ( "flag" "fmt" "os" - "os/user" "strconv" - "strings" "golang.org/x/term" @@ -150,6 +148,8 @@ func runUpdate(pb libpolybase.Polybase, ctx context.Context, args []string) erro partial.Total = newTotal case "s": partial.Semester = newSemester + default: + panic(errors.Join(ErrInvalidUsage, fmt.Errorf("unknown flag %s", f.Name))) } }) @@ -315,17 +315,3 @@ func runVisibility(pb libpolybase.Polybase, ctx context.Context, args []string) return printCourse(updated, *jsonOutput) } - -func getCurrentUser() string { - currentUser, err := user.Current() - if err != nil { - return "unknown-user" - } - - // Extract just the username part, removing domain if present - username := currentUser.Username - if i := strings.LastIndex(username, "\\"); i >= 0 { - username = username[i+1:] - } - return username -} diff --git a/polybase/main.go b/polybase/main.go index 46635ea..2a126ac 100644 --- a/polybase/main.go +++ b/polybase/main.go @@ -6,6 +6,8 @@ import ( "errors" "flag" "fmt" + "os/user" + "strings" "github.com/alias-asso/polybase-go/libpolybase" _ "modernc.org/sqlite" @@ -101,3 +103,17 @@ func dispatch(pb libpolybase.Polybase, args []string) error { return errors.Join(ErrUnknownCommand, fmt.Errorf("command %s not supported", cmd)) } } + +func getCurrentUser() string { + currentUser, err := user.Current() + if err != nil { + return "unknown-user" + } + + // Extract just the username part, removing domain if present + username := currentUser.Username + if i := strings.LastIndex(username, "\\"); i >= 0 { + username = username[i+1:] + } + return username +} From 658627f3cf4623507a463bc982207c60c6db62c2 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Thu, 12 Feb 2026 11:14:03 +0100 Subject: [PATCH 6/8] style(cli): move context to first param follow go conventions and standards --- polybase/commands.go | 14 +++++++------- polybase/main.go | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/polybase/commands.go b/polybase/commands.go index ceeb703..0759d6d 100644 --- a/polybase/commands.go +++ b/polybase/commands.go @@ -29,7 +29,7 @@ func scope(args []string, usage func()) ([]string, string, string, uint8, error) return args[3:], args[0], args[1], uint8(part), nil } -func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) error { +func runCreate(ctx context.Context, pb libpolybase.Polybase, args []string) error { flags := flag.NewFlagSet("create", flag.ExitOnError) flags.Usage = createUsage(flags) @@ -74,7 +74,7 @@ func runCreate(pb libpolybase.Polybase, ctx context.Context, args []string) erro return printCourse(created, *jsonOutput) } -func runGet(pb libpolybase.Polybase, ctx context.Context, args []string) error { +func runGet(ctx context.Context, pb libpolybase.Polybase, args []string) error { flags := flag.NewFlagSet("get", flag.ExitOnError) flags.Usage = getUsage(flags) @@ -103,7 +103,7 @@ func runGet(pb libpolybase.Polybase, ctx context.Context, args []string) error { return printCourse(course, *jsonOutput) } -func runUpdate(pb libpolybase.Polybase, ctx context.Context, args []string) error { +func runUpdate(ctx context.Context, pb libpolybase.Polybase, args []string) error { flags := flag.NewFlagSet("update", flag.ExitOnError) flags.Usage = updateUsage(flags) @@ -162,7 +162,7 @@ func runUpdate(pb libpolybase.Polybase, ctx context.Context, args []string) erro return printCourse(updated, *jsonOutput) } -func runDelete(pb libpolybase.Polybase, ctx context.Context, args []string) error { +func runDelete(ctx context.Context, pb libpolybase.Polybase, args []string) error { args, code, kind, part, err := scope(args, deleteUsage(nil)) if err != nil { return err @@ -207,7 +207,7 @@ func runDelete(pb libpolybase.Polybase, ctx context.Context, args []string) erro return pb.DeleteCourse(ctx, username, id) } -func runList(pb libpolybase.Polybase, ctx context.Context, args []string) error { +func runList(ctx context.Context, pb libpolybase.Polybase, args []string) error { flags := flag.NewFlagSet("list", flag.ExitOnError) flags.Usage = listUsage(flags) @@ -245,7 +245,7 @@ func runList(pb libpolybase.Polybase, ctx context.Context, args []string) error return printCourses(courses, *jsonOutput) } -func runQuantity(pb libpolybase.Polybase, ctx context.Context, args []string) error { +func runQuantity(ctx context.Context, pb libpolybase.Polybase, args []string) error { flags := flag.NewFlagSet("get", flag.ExitOnError) flags.Usage = quantityUsage(flags) @@ -285,7 +285,7 @@ func runQuantity(pb libpolybase.Polybase, ctx context.Context, args []string) er return printCourse(updated, *jsonOutput) } -func runVisibility(pb libpolybase.Polybase, ctx context.Context, args []string) error { +func runVisibility(ctx context.Context, pb libpolybase.Polybase, args []string) error { flags := flag.NewFlagSet("visibility", flag.ExitOnError) flags.Usage = visibilityUsage(flags) diff --git a/polybase/main.go b/polybase/main.go index 2a126ac..91bd9ce 100644 --- a/polybase/main.go +++ b/polybase/main.go @@ -85,19 +85,19 @@ func dispatch(pb libpolybase.Polybase, args []string) error { switch cmd { case "create": - return runCreate(pb, ctx, cmdArgs) + return runCreate(ctx, pb, cmdArgs) case "get": - return runGet(pb, ctx, cmdArgs) + return runGet(ctx, pb, cmdArgs) case "update": - return runUpdate(pb, ctx, cmdArgs) + return runUpdate(ctx, pb, cmdArgs) case "delete": - return runDelete(pb, ctx, cmdArgs) + return runDelete(ctx, pb, cmdArgs) case "list": - return runList(pb, ctx, cmdArgs) + return runList(ctx, pb, cmdArgs) case "quantity": - return runQuantity(pb, ctx, cmdArgs) + return runQuantity(ctx, pb, cmdArgs) case "visibility": - return runVisibility(pb, ctx, cmdArgs) + return runVisibility(ctx, pb, cmdArgs) default: printUsage() return errors.Join(ErrUnknownCommand, fmt.Errorf("command %s not supported", cmd)) From 91918e1457f4887f6471b47752b44c6b864b455e Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Thu, 12 Feb 2026 11:17:05 +0100 Subject: [PATCH 7/8] fix(cli): using old usage in create --- polybase/commands.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/polybase/commands.go b/polybase/commands.go index 0759d6d..53faab2 100644 --- a/polybase/commands.go +++ b/polybase/commands.go @@ -49,7 +49,7 @@ func runCreate(ctx context.Context, pb libpolybase.Polybase, args []string) erro } if *name == "" || *quantity == -1 || *semester == "" { - createUsage(flags) + flags.Usage() return errors.Join(ErrInvalidUsage, fmt.Errorf("name (-n), quantity (-q) and semester (-s) are required")) } @@ -234,6 +234,8 @@ func runList(ctx context.Context, pb libpolybase.Polybase, args []string) error filterKind = kind case "p": filterPart = part + default: + panic(errors.Join(ErrInvalidUsage, fmt.Errorf("unknown flag %s", f.Name))) } }) From e80bf8c7ab35750f3159a7a3847af0d5f203f31d Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Thu, 12 Feb 2026 13:15:55 +0100 Subject: [PATCH 8/8] refactor(cli): rewrite parse args in polybased create option skip-ldap --- polybased/args.go | 77 -------------------------------------- polybased/config/config.go | 52 ++++++++++++------------- polybased/main.go | 55 ++++++++++++++++++++++----- polybased/version.go | 9 ----- 4 files changed, 72 insertions(+), 121 deletions(-) delete mode 100644 polybased/args.go delete mode 100644 polybased/version.go diff --git a/polybased/args.go b/polybased/args.go deleted file mode 100644 index 4b211ab..0000000 --- a/polybased/args.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" -) - -type Args struct { - ConfigPath string - ShowHelp bool - ShowVersion bool -} - -func parseArgs() (*Args, error) { - args := &Args{ - ConfigPath: "/etc/polybase/polybase.cfg", - } - - // If no arguments provided, return defaults - if len(os.Args) <= 1 { - return args, nil - } - - // First pass: check for help flag - for _, arg := range os.Args[1:] { - if arg == "-h" || arg == "--help" { - printUsage() - os.Exit(0) - } - } - - // Second pass: process other flags - osArgs := os.Args[1:] - for i := 0; i < len(osArgs); i++ { - arg := osArgs[i] - - switch arg { - case "-v": - args.ShowVersion = true - return args, nil - - case "-c": - if i+1 >= len(osArgs) { - return nil, fmt.Errorf("error: -c requires a path argument") - } - nextArg := osArgs[i+1] - if strings.HasPrefix(nextArg, "-") { - return nil, fmt.Errorf("error: -c requires a path argument") - } - args.ConfigPath = nextArg - i++ - - default: - if strings.HasPrefix(arg, "-") { - return nil, fmt.Errorf("error: unknown flag: %s", arg) - } - } - } - - return args, nil -} - -func printUsage() { - fmt.Printf(`Usage: polybased [OPTIONS] - -Manage polybase database from the web browser. - -Options: - -c Path to config file (default: /etc/polybase/config.cfg) - -v Print version information - -h Print this help message - -For bug reporting and more information, please see: -https://github.com/alias-asso/polybase-go -`) -} diff --git a/polybased/config/config.go b/polybased/config/config.go index 0548eb2..52d3a40 100644 --- a/polybased/config/config.go +++ b/polybased/config/config.go @@ -58,7 +58,7 @@ func DefaultConfig() Config { } } -func LoadConfig(configPath string) (Config, error) { +func LoadConfig(configPath string, skipLdap bool) (Config, error) { config := DefaultConfig() if _, err := toml.DecodeFile(configPath, &config); err != nil { return Config{}, err @@ -66,7 +66,7 @@ func LoadConfig(configPath string) (Config, error) { config.loadFromEnv() - if err := config.Validate(); err != nil { + if err := config.Validate(skipLdap); err != nil { return Config{}, fmt.Errorf("invalid configuration: %w", err) } @@ -109,7 +109,7 @@ func (c *Config) loadFromEnv() { } } -func (c *Config) Validate() error { +func (c *Config) Validate(skipLdap bool) error { // Server validation if c.Server.Host == "" { return fmt.Errorf("server.host is required") @@ -138,29 +138,31 @@ func (c *Config) Validate() error { return fmt.Errorf("database.path is required") } - // LDAP validation - if c.LDAP.Host == "" { - return fmt.Errorf("ldap.host is required") + if !skipLdap { + // LDAP validation + if c.LDAP.Host == "" { + return fmt.Errorf("ldap.host is required") + } + if c.LDAP.Port == "" { + return fmt.Errorf("ldap.port is required") + } + if port, err := strconv.Atoi(c.LDAP.Port); err != nil || port < 1 || port > 65535 { + return fmt.Errorf("ldap.port must be a valid port number (1-65535)") + } + if c.LDAP.UserDN == "" { + return fmt.Errorf("ldap.user_dn is required") + } + if !strings.Contains(c.LDAP.UserDN, "%s") { + return fmt.Errorf("ldap.user_dn must contain %%s placeholder for username") + } + + // Test LDAP connection + l, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%s", c.LDAP.Host, c.LDAP.Port)) + if err != nil { + return fmt.Errorf("failed to connect to LDAP server: %w", err) + } + defer l.Close() } - if c.LDAP.Port == "" { - return fmt.Errorf("ldap.port is required") - } - if port, err := strconv.Atoi(c.LDAP.Port); err != nil || port < 1 || port > 65535 { - return fmt.Errorf("ldap.port must be a valid port number (1-65535)") - } - if c.LDAP.UserDN == "" { - return fmt.Errorf("ldap.user_dn is required") - } - if !strings.Contains(c.LDAP.UserDN, "%s") { - return fmt.Errorf("ldap.user_dn must contain %%s placeholder for username") - } - - // Test LDAP connection - l, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%s", c.LDAP.Host, c.LDAP.Port)) - if err != nil { - return fmt.Errorf("failed to connect to LDAP server: %w", err) - } - defer l.Close() // Auth validation if c.Auth.JWTSecret == "" { diff --git a/polybased/main.go b/polybased/main.go index a51f5b7..be7806c 100644 --- a/polybased/main.go +++ b/polybased/main.go @@ -1,30 +1,45 @@ package main import ( + "flag" + "fmt" "log" - "os" "github.com/alias-asso/polybase-go/polybased/config" "github.com/alias-asso/polybase-go/polybased/routes" ) +const version = "0.1.0" + +var ( + showHelp bool = false + showVersion bool = false + skipLdap bool = false + configPath string = "" +) + +func init() { + flag.BoolVar(&showHelp, "h", showHelp, "show the help") + flag.BoolVar(&showHelp, "help", showHelp, "show the help") + flag.BoolVar(&showVersion, "v", showVersion, "show the version") + flag.BoolVar(&skipLdap, "skip-ldap", skipLdap, "skip ldap checks") + flag.StringVar(&configPath, "c", configPath, "set the config path") +} + func main() { - args, err := parseArgs() - if err != nil { - log.Fatal(err) - } + flag.Parse() - if args.ShowHelp { + if showHelp { printUsage() - os.Exit(0) + return } - if args.ShowVersion { + if showVersion { printVersion() - os.Exit(0) + return } - config, err := config.LoadConfig(args.ConfigPath) + config, err := config.LoadConfig(configPath, skipLdap) if err != nil { log.Fatalf("Failed to load config: %v", err) } @@ -35,3 +50,23 @@ func main() { } srv.Run() } + +func printUsage() { + fmt.Printf(`Usage: polybased [OPTIONS] + +Manage polybase database from the web browser. + +Options: + -c Path to config file (default: /etc/polybase/config.cfg) + -v Print version information + -h Print this help message + -skip-ldap Skip LDAP verification + +For bug reporting and more information, please see: +https://github.com/alias-asso/polybase-go +`) +} + +func printVersion() { + fmt.Printf("polybased version %s\n", version) +} diff --git a/polybased/version.go b/polybased/version.go deleted file mode 100644 index 5882056..0000000 --- a/polybased/version.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import "fmt" - -const version = "0.1.0" - -func printVersion() { - fmt.Printf("polybased version %s\n", version) -}