diff --git a/cmd.go b/cmd.go index c1f5d6b7..acb27be5 100644 --- a/cmd.go +++ b/cmd.go @@ -26,6 +26,59 @@ import ( "github.com/Jguer/yay/v13/pkg/vcs" ) +type helpOption struct { + flags string + description string +} + +type operationHelp struct { + short string + long string + targets string + options []helpOption +} + +var yayOperationHelp = []operationHelp{ + {"B", "build", "[options] [dir]", []helpOption{ + {"-i --install", "Build and install a PKGBUILD"}, + }}, + {"G", "getpkgbuild", "[options] [package(s)]", []helpOption{ + {"-f --force", "Force download for existing ABS packages"}, + {"-p --print", "Print pkgbuild of packages"}, + }}, + {"P", "show", "[options]", []helpOption{ + {"-c --complete", "Used for completions"}, + {"-d --defaultconfig", "Print default yay configuration"}, + {"-g --currentconfig", "Print current yay configuration"}, + {"-s --stats", "Display system package statistics"}, + {"-w --news", "Print arch news"}, + {"-q --quiet", "Only show titles when printing news"}, + }}, + {"W", "web", "[options] [package(s)]", []helpOption{ + {"-u --unvote", "Remove a vote from AUR package(s)"}, + {"-v --vote", "Vote for AUR package(s)"}, + }}, + {"Y", "yay", "[options] [package(s)]", []helpOption{ + {"-c --clean", "Remove unneeded dependencies (-cc to ignore optdepends)"}, + {" --gendb", "Generates development package DB used for updating"}, + }}, +} + +func (h *operationHelp) syntax() string { + return fmt.Sprintf("yay {-%s --%s}", h.short, h.long) +} + +func findYayOperationHelp(operation string) *operationHelp { + for i := range yayOperationHelp { + help := &yayOperationHelp[i] + if operation == help.short || operation == help.long { + return help + } + } + + return nil +} + func usage(logger *text.Logger) { logger.Println(`Usage: yay @@ -41,15 +94,17 @@ operations: yay {-R --remove} [options] yay {-S --sync} [options] [package(s)] yay {-T --deptest} [options] [package(s)] - yay {-U --upgrade} [options] + yay {-U --upgrade} [options] `) -New operations: - yay {-B --build} [options] [dir] - yay {-G --getpkgbuild} [options] [package(s)] - yay {-P --show} [options] - yay {-W --web} [options] [package(s)] - yay {-Y --yay} [options] [package(s)] + // Yay-specific operations + logger.Println("\nNew operations:") + for i := range yayOperationHelp { + help := &yayOperationHelp[i] + logger.Printf(" %-22s %s\n", help.syntax(), help.targets) + } + logger.Println("\nuse 'yay {-h --help}' with an operation for available options") + logger.Println(` If no operation is specified 'yay -Syu' will be performed If no operation is specified and targets are provided, -Y will be assumed @@ -118,27 +173,29 @@ Permanent configuration options: --sudo sudo command to use --sudoflags Pass arguments to sudo - --sudoloop Loop sudo calls in the background to avoid timeout + --sudoloop Loop sudo calls in the background to avoid timeout`) +} -show specific options (used with -P): - -c --complete Used for completions - -d --defaultconfig Print default yay configuration - -g --currentconfig Print current yay configuration - -s --stats Display system package statistics - -w --news Print arch news +func operationUsage(logger *text.Logger, operation string) { + help := findYayOperationHelp(operation) + if help == nil { + return + } -yay specific options (used with -Y): - -c --clean Remove unneeded dependencies (-cc to ignore optdepends) - --gendb Generates development package DB used for updating - -getpkgbuild specific options (used with -G): - -f --force Force download for existing ABS packages - -p --print Print pkgbuild of packages`) + logger.Printf("Usage: %s %s\noptions:\n", help.syntax(), help.targets) + for _, option := range help.options { + logger.Printf(" %-21s %s\n", option.flags, option.description) + } } func handleCmd(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor, ) error { + if cmdArgs.Op == "V" || cmdArgs.Op == "version" { + handleVersion(run.Logger) + return nil + } + if cmdArgs.ExistsArg("h", "help") { return handleHelp(ctx, run, cmdArgs) } @@ -148,9 +205,6 @@ func handleCmd(ctx context.Context, run *runtime.Runtime, } switch cmdArgs.Op { - case "V", "version": - handleVersion(run.Logger) - return nil case "D", "database": return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) @@ -233,13 +287,15 @@ func handleQuery(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Argu } func handleHelp(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments) error { - usage(run.Logger) switch cmdArgs.Op { case "Y", "yay", "G", "getpkgbuild", "P", "show", "W", "web", "B", "build": + operationUsage(run.Logger, cmdArgs.Op) + return nil + case "": + usage(run.Logger) return nil } - run.Logger.Println("\npacman operation specific options:") return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) } diff --git a/cmd_test.go b/cmd_test.go index 04903203..11d2dae9 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -3,6 +3,7 @@ package main import ( + "bytes" "context" "fmt" "io" @@ -26,6 +27,42 @@ import ( "github.com/Jguer/yay/v13/pkg/vcs" ) +func TestHandleHelp(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + operation string + wantOutput string + wantCalls int + }{ + {"top-level", "", "Usage:\n yay", 0}, + {"yay operation", "Y", "Usage: yay {-Y --yay}", 0}, + {"pacman operation", "S", "", 1}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var output bytes.Buffer + logger := text.NewLogger(&output, &output, strings.NewReader(""), false, "test") + runner := &exe.MockRunner{} + cmdArgs := parser.MakeArguments() + cmdArgs.Op = tt.operation + require.NoError(t, cmdArgs.AddArg("help")) + run := &runtime.Runtime{ + Cfg: &settings.Configuration{Mode: parser.ModeAny}, + CmdBuilder: &exe.CmdBuilder{PacmanBin: "pacman", PacmanConfigPath: "/etc/pacman.conf", + PacmanDBPath: t.TempDir(), Runner: runner, Log: logger}, + Logger: logger, + } + + require.NoError(t, handleHelp(t.Context(), run, cmdArgs)) + assert.Contains(t, output.String(), tt.wantOutput) + assert.Len(t, runner.ShowCalls, tt.wantCalls) + }) + } +} + func TestYogurtMenuAURDB(t *testing.T) { t.Skip("skip until Operation service is an interface") t.Parallel() diff --git a/pkg/settings/parser/parser.go b/pkg/settings/parser/parser.go index 1f0927b7..a0e73e7c 100644 --- a/pkg/settings/parser/parser.go +++ b/pkg/settings/parser/parser.go @@ -654,6 +654,10 @@ func (a *Arguments) Parse() error { } if a.Op == "" { + if a.ExistsArg("h", "help") { + return nil + } + if len(a.Targets) > 0 { a.Op = "Y" } else { diff --git a/pkg/settings/parser/parser_test.go b/pkg/settings/parser/parser_test.go index 99ab790c..a53d8076 100644 --- a/pkg/settings/parser/parser_test.go +++ b/pkg/settings/parser/parser_test.go @@ -363,3 +363,13 @@ func TestArguments_ParseStdin_broken_pipe(t *testing.T) { err = args.parseStdin() assert.Error(t, err) } + +func TestArguments_ParseTopLevelHelp(t *testing.T) { + originalArgs := os.Args + t.Cleanup(func() { os.Args = originalArgs }) + os.Args = []string{"yay", "--help"} + + args := MakeArguments() + require.NoError(t, args.Parse()) + assert.Empty(t, args.Op) +}