Loading cli.go +58 −1 Changes for cli.go: 58 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ package main import ( "fmt" "strconv" "strings" "github.com/spf13/cobra" ) Loading Loading @@ -221,14 +223,23 @@ func newSyncCmd() *cobra.Command { return cmd } // newBasexCmd is intentionally wired differently from the other climax // commands: BaseX has its own CLI flags, so Cobra must not parse or reject // unknown options before Java sees them. Only climax-owned options are handled // here; everything else is forwarded to org.basex.BaseX. func newBasexCmd() *cobra.Command { directory := cwd() verbose := false cmd := &cobra.Command{ Use: "basex", Short: T("cmd.basex.short"), DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { return runBasex(directory, verbose) opts, err := parseBasexArgs(args, directory, verbose) if err != nil { return err } return runBasex(opts.directory, opts.verbose, opts.forwarded) }, } cmd.Flags().StringVar(&directory, "directory", directory, T("flag.common.directory")) Loading @@ -236,6 +247,52 @@ func newBasexCmd() *cobra.Command { return cmd } type basexArgs struct { directory string verbose bool forwarded []string } // parseBasexArgs recognizes only climax's wrapper options and leaves BaseX // arguments untouched. --directory and --verbose affect climax; -- stops // wrapper parsing; --help is deliberately forwarded to BaseX, so local command // help remains available via `climax help basex`. func parseBasexArgs(args []string, directory string, verbose bool) (basexArgs, error) { opts := basexArgs{ directory: directory, verbose: verbose, forwarded: make([]string, 0, len(args)), } for i := 0; i < len(args); i++ { arg := args[i] switch { case arg == "--": opts.forwarded = append(opts.forwarded, args[i+1:]...) return opts, nil case arg == "--directory": if i+1 >= len(args) { return opts, fmt.Errorf(T("err.flag_needs_argument"), "--directory") } i++ opts.directory = args[i] case strings.HasPrefix(arg, "--directory="): opts.directory = strings.TrimPrefix(arg, "--directory=") case arg == "--verbose": opts.verbose = true case strings.HasPrefix(arg, "--verbose="): value := strings.TrimPrefix(arg, "--verbose=") parsed, err := strconv.ParseBool(value) if err != nil { return opts, fmt.Errorf(T("err.invalid_bool_flag"), value, "--verbose") } opts.verbose = parsed default: opts.forwarded = append(opts.forwarded, arg) } } return opts, nil } func newBasexGUICmd() *cobra.Command { directory := cwd() verbose := false Loading cli_commands_test.go +1 −1 Changes for cli_commands_test.go: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -128,7 +128,7 @@ func TestHelpRunsForAllCLICommands(t *testing.T) { {"bundles-remove", "dummy", "--help"}, {"feed", "dummy", "--help"}, {"sync", "--help"}, {"basex", "--help"}, {"help", "basex"}, {"basex-gui", "--help"}, {"templates-list", "--help"}, {"static-list", "--help"}, Loading commands.go +2 −2 Changes for commands.go: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -408,12 +408,12 @@ func runSync(directory string) error { return errors.New(T("err.no_max_use_new")) } func runBasex(directory string, verbose bool) error { func runBasex(directory string, verbose bool, args []string) error { abs, _, err := loadProject(directory) if err != nil { return err } cmd, err := newBaseXCommand(abs, "org.basex.BaseX") cmd, err := newBaseXCommand(abs, "org.basex.BaseX", args...) if err != nil { return err } Loading i18n.go +4 −0 Changes for i18n.go: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -129,6 +129,8 @@ commandes new, projects et cache-clear qui peuvent être lancées aussi en dehor "err.invalid_max_archive": "archive MaX invalide ou non prise en charge: %s", "err.invalid_mirrored_path": "chemin de copie statique invalide %s", "err.not_max_config": "%s n'est pas un fichier de configuration MaX", "err.flag_needs_argument": "l'option %s attend une valeur", "err.invalid_bool_flag": "valeur booléenne invalide %q pour %s", }, localeEN: { "app.short": "CLI tool for MaX", Loading Loading @@ -245,6 +247,8 @@ which can also be run outside a MaX directory.`, "err.invalid_max_archive": "invalid or unsupported MaX archive: %s", "err.invalid_mirrored_path": "invalid mirrored path %s", "err.not_max_config": "%s is not a MaX configuration file", "err.flag_needs_argument": "flag %s needs an argument", "err.invalid_bool_flag": "invalid boolean value %q for %s", }, } Loading main_test.go +56 −0 Changes for main_test.go: 56 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1052,6 +1052,62 @@ func TestBasexGUICommandRunsGUIClass(t *testing.T) { } } func TestBasexCommandForwardsBaseXFlags(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("test uses a shell script as fake bundled java") } tmpHome := t.TempDir() prevHome := userMaxDir prevCache := cacheDir userMaxDir = tmpHome cacheDir = filepath.Join(userMaxDir, "cache") t.Cleanup(func() { userMaxDir = prevHome cacheDir = prevCache }) projectDir := filepath.Join(t.TempDir(), "project") if err := os.MkdirAll(projectDir, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(projectDir, maxConfigFile), []byte(configInitTemplate), 0o644); err != nil { t.Fatal(err) } logPath := filepath.Join(t.TempDir(), "java.log") t.Setenv("CLIMAX_TEST_JAVA_LOG", logPath) if err := installFakeBundledJava(tmpHome); err != nil { t.Fatal(err) } root := newRootCmd() root.SetArgs([]string{ "basex", "--directory", projectDir, "--help", "-q", "1", "--serialize", "method=json", }) if err := root.Execute(); err != nil { t.Fatalf("basex command failed: %v", err) } lines, err := readLogLines(logPath) if err != nil { t.Fatal(err) } if len(lines) != 1 { t.Fatalf("expected one java invocation, got %d:\n%v", len(lines), lines) } if !strings.Contains(lines[0], "org.basex.BaseX --help -q 1 --serialize method=json") { t.Fatalf("expected BaseX flags to be forwarded, got:\n%s", lines[0]) } if strings.Contains(lines[0], "--directory "+projectDir) { t.Fatalf("expected climax --directory flag not to be forwarded, got:\n%s", lines[0]) } } // Ensures bundle installation runs with the project as the working directory, // regardless of where the CLI process was started. func TestBundleInstallRunsFromProjectDirectory(t *testing.T) { Loading Loading
cli.go +58 −1 Changes for cli.go: 58 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ package main import ( "fmt" "strconv" "strings" "github.com/spf13/cobra" ) Loading Loading @@ -221,14 +223,23 @@ func newSyncCmd() *cobra.Command { return cmd } // newBasexCmd is intentionally wired differently from the other climax // commands: BaseX has its own CLI flags, so Cobra must not parse or reject // unknown options before Java sees them. Only climax-owned options are handled // here; everything else is forwarded to org.basex.BaseX. func newBasexCmd() *cobra.Command { directory := cwd() verbose := false cmd := &cobra.Command{ Use: "basex", Short: T("cmd.basex.short"), DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { return runBasex(directory, verbose) opts, err := parseBasexArgs(args, directory, verbose) if err != nil { return err } return runBasex(opts.directory, opts.verbose, opts.forwarded) }, } cmd.Flags().StringVar(&directory, "directory", directory, T("flag.common.directory")) Loading @@ -236,6 +247,52 @@ func newBasexCmd() *cobra.Command { return cmd } type basexArgs struct { directory string verbose bool forwarded []string } // parseBasexArgs recognizes only climax's wrapper options and leaves BaseX // arguments untouched. --directory and --verbose affect climax; -- stops // wrapper parsing; --help is deliberately forwarded to BaseX, so local command // help remains available via `climax help basex`. func parseBasexArgs(args []string, directory string, verbose bool) (basexArgs, error) { opts := basexArgs{ directory: directory, verbose: verbose, forwarded: make([]string, 0, len(args)), } for i := 0; i < len(args); i++ { arg := args[i] switch { case arg == "--": opts.forwarded = append(opts.forwarded, args[i+1:]...) return opts, nil case arg == "--directory": if i+1 >= len(args) { return opts, fmt.Errorf(T("err.flag_needs_argument"), "--directory") } i++ opts.directory = args[i] case strings.HasPrefix(arg, "--directory="): opts.directory = strings.TrimPrefix(arg, "--directory=") case arg == "--verbose": opts.verbose = true case strings.HasPrefix(arg, "--verbose="): value := strings.TrimPrefix(arg, "--verbose=") parsed, err := strconv.ParseBool(value) if err != nil { return opts, fmt.Errorf(T("err.invalid_bool_flag"), value, "--verbose") } opts.verbose = parsed default: opts.forwarded = append(opts.forwarded, arg) } } return opts, nil } func newBasexGUICmd() *cobra.Command { directory := cwd() verbose := false Loading
cli_commands_test.go +1 −1 Changes for cli_commands_test.go: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -128,7 +128,7 @@ func TestHelpRunsForAllCLICommands(t *testing.T) { {"bundles-remove", "dummy", "--help"}, {"feed", "dummy", "--help"}, {"sync", "--help"}, {"basex", "--help"}, {"help", "basex"}, {"basex-gui", "--help"}, {"templates-list", "--help"}, {"static-list", "--help"}, Loading
commands.go +2 −2 Changes for commands.go: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -408,12 +408,12 @@ func runSync(directory string) error { return errors.New(T("err.no_max_use_new")) } func runBasex(directory string, verbose bool) error { func runBasex(directory string, verbose bool, args []string) error { abs, _, err := loadProject(directory) if err != nil { return err } cmd, err := newBaseXCommand(abs, "org.basex.BaseX") cmd, err := newBaseXCommand(abs, "org.basex.BaseX", args...) if err != nil { return err } Loading
i18n.go +4 −0 Changes for i18n.go: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -129,6 +129,8 @@ commandes new, projects et cache-clear qui peuvent être lancées aussi en dehor "err.invalid_max_archive": "archive MaX invalide ou non prise en charge: %s", "err.invalid_mirrored_path": "chemin de copie statique invalide %s", "err.not_max_config": "%s n'est pas un fichier de configuration MaX", "err.flag_needs_argument": "l'option %s attend une valeur", "err.invalid_bool_flag": "valeur booléenne invalide %q pour %s", }, localeEN: { "app.short": "CLI tool for MaX", Loading Loading @@ -245,6 +247,8 @@ which can also be run outside a MaX directory.`, "err.invalid_max_archive": "invalid or unsupported MaX archive: %s", "err.invalid_mirrored_path": "invalid mirrored path %s", "err.not_max_config": "%s is not a MaX configuration file", "err.flag_needs_argument": "flag %s needs an argument", "err.invalid_bool_flag": "invalid boolean value %q for %s", }, } Loading
main_test.go +56 −0 Changes for main_test.go: 56 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1052,6 +1052,62 @@ func TestBasexGUICommandRunsGUIClass(t *testing.T) { } } func TestBasexCommandForwardsBaseXFlags(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("test uses a shell script as fake bundled java") } tmpHome := t.TempDir() prevHome := userMaxDir prevCache := cacheDir userMaxDir = tmpHome cacheDir = filepath.Join(userMaxDir, "cache") t.Cleanup(func() { userMaxDir = prevHome cacheDir = prevCache }) projectDir := filepath.Join(t.TempDir(), "project") if err := os.MkdirAll(projectDir, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(projectDir, maxConfigFile), []byte(configInitTemplate), 0o644); err != nil { t.Fatal(err) } logPath := filepath.Join(t.TempDir(), "java.log") t.Setenv("CLIMAX_TEST_JAVA_LOG", logPath) if err := installFakeBundledJava(tmpHome); err != nil { t.Fatal(err) } root := newRootCmd() root.SetArgs([]string{ "basex", "--directory", projectDir, "--help", "-q", "1", "--serialize", "method=json", }) if err := root.Execute(); err != nil { t.Fatalf("basex command failed: %v", err) } lines, err := readLogLines(logPath) if err != nil { t.Fatal(err) } if len(lines) != 1 { t.Fatalf("expected one java invocation, got %d:\n%v", len(lines), lines) } if !strings.Contains(lines[0], "org.basex.BaseX --help -q 1 --serialize method=json") { t.Fatalf("expected BaseX flags to be forwarded, got:\n%s", lines[0]) } if strings.Contains(lines[0], "--directory "+projectDir) { t.Fatalf("expected climax --directory flag not to be forwarded, got:\n%s", lines[0]) } } // Ensures bundle installation runs with the project as the working directory, // regardless of where the CLI process was started. func TestBundleInstallRunsFromProjectDirectory(t *testing.T) { Loading