Files
yay/cmd.go
T
Joey HoltzmanandJo e56f2cb911 feat(translate): add translation for help message (#2944)
Allow for the help message to be localized. This changes the help
message printing so that every message is wrapped in `gotext.Get()`.

Co-authored-by: Jo <me@jguer.space>
2026-08-07 12:36:56 +09:00

545 lines
19 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"net/http"
"slices"
"strings"
alpm "github.com/Jguer/dyalpm"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v13/pkg/completion"
"github.com/Jguer/yay/v13/pkg/db"
"github.com/Jguer/yay/v13/pkg/download"
"github.com/Jguer/yay/v13/pkg/intrange"
"github.com/Jguer/yay/v13/pkg/news"
"github.com/Jguer/yay/v13/pkg/query"
"github.com/Jguer/yay/v13/pkg/runtime"
"github.com/Jguer/yay/v13/pkg/settings"
"github.com/Jguer/yay/v13/pkg/settings/exe"
"github.com/Jguer/yay/v13/pkg/settings/parser"
"github.com/Jguer/yay/v13/pkg/text"
"github.com/Jguer/yay/v13/pkg/upgrade"
"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
}
type usageOption struct {
flags string
description string
}
func yayOperationHelp() []operationHelp {
strOptions := gotext.Get("options")
strDir := gotext.Get("dir")
strPackage := gotext.Get("package(s)")
return []operationHelp{
{"B", "build", fmt.Sprintf("[%s] [%s]", strOptions, strDir), []helpOption{
{"-i --install", gotext.Get("Build and install a PKGBUILD")},
}},
{"G", "getpkgbuild", fmt.Sprintf("[%s] [%s]", strOptions, strPackage), []helpOption{
{"-f --force", gotext.Get("Force download for existing ABS packages")},
{"-p --print", gotext.Get("Print pkgbuild of packages")},
}},
{"P", "show", fmt.Sprintf("[%s]", strOptions), []helpOption{
{"-c --complete", gotext.Get("Used for completions")},
{"-d --defaultconfig", gotext.Get("Print default yay configuration")},
{"-g --currentconfig", gotext.Get("Print current yay configuration")},
{"-s --stats", gotext.Get("Display system package statistics")},
{"-w --news", gotext.Get("Print arch news")},
{"-q --quiet", gotext.Get("Only show titles when printing news")},
}},
{"W", "web", fmt.Sprintf("[%s] [%s]", strOptions, strPackage), []helpOption{
{"-u --unvote", gotext.Get("Remove a vote from AUR package(s)")},
{"-v --vote", gotext.Get("Vote for AUR package(s)")},
}},
{"Y", "yay", fmt.Sprintf("[%s] [%s]", strOptions, strPackage), []helpOption{
{"-c --clean", gotext.Get("Remove unneeded dependencies (-cc to ignore optdepends)")},
{" --gendb", gotext.Get("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 {
operations := yayOperationHelp()
for i := range operations {
help := &operations[i]
if operation == help.short || operation == help.long {
return help
}
}
return nil
}
func printUsageOptions(logger *text.Logger, title string, options []usageOption) {
logger.Println("\n" + title)
for _, option := range options {
if option.flags == "" && option.description == "" {
logger.Println("")
continue
}
descriptionLines := strings.Split(option.description, "\n")
logger.Printf(" %-21s %s\n", option.flags, descriptionLines[0])
for _, line := range descriptionLines[1:] {
logger.Printf(" %-21s %s\n", "", line)
}
}
}
func usage(logger *text.Logger) {
strFile := gotext.Get("file(s)")
strOperation := gotext.Get("operation")
strOptions := gotext.Get("options")
strPackage := gotext.Get("package(s)")
logger.Println(gotext.Get("Usage:"))
logger.Println(" yay")
logger.Printf(" yay <%s> [...]\n", strOperation)
logger.Printf(" yay <%s>\n", strPackage)
logger.Println("\n" + gotext.Get("operations:"))
logger.Println(" yay {-h --help}")
logger.Println(" yay {-V --version}")
logger.Printf(" yay {-D --database} <%s> <%s>\n", strOptions, strPackage)
logger.Printf(" yay {-F --files} [%s] [%s]\n", strOptions, strPackage)
logger.Printf(" yay {-Q --query} [%s] [%s]\n", strOptions, strPackage)
logger.Printf(" yay {-R --remove} [%s] <%s>\n", strOptions, strPackage)
logger.Printf(" yay {-S --sync} [%s] [%s]\n", strOptions, strPackage)
logger.Printf(" yay {-T --deptest} [%s] [%s]\n", strOptions, strPackage)
logger.Printf(" yay {-U --upgrade} [%s] <%s>\n", strOptions, strFile)
// Yay-specific operations
logger.Println("\n" + gotext.Get("New operations:"))
operations := yayOperationHelp()
for i := range operations {
help := &operations[i]
logger.Printf(" %-22s %s\n", help.syntax(), help.targets)
}
logger.Println("\n" + gotext.Get("use 'yay {-h --help}' with an operation for available options"))
logger.Println("\n" + gotext.Get("If no operation is specified 'yay -Syu' will be performed"))
logger.Println(gotext.Get("If no operation is specified and targets are provided, -Y will be assumed"))
printUsageOptions(logger, gotext.Get("New options:"), []usageOption{
{"-N --repo", gotext.Get("Assume targets are from the repositories")},
{"-a --aur", gotext.Get("Assume targets are from the AUR")},
})
printUsageOptions(logger, gotext.Get("Permanent configuration options:"), []usageOption{
{"--save", gotext.Get("Causes the following options to be saved back to the\nconfig file when used")},
{},
{"--aururl <url>", gotext.Get("Set an alternative AUR URL")},
{"--aurrpcurl <url>", gotext.Get("Set an alternative URL for the AUR /rpc endpoint")},
{"--builddir <dir>", gotext.Get("Directory used to download and run PKGBUILDS")},
{"--editor <file>", gotext.Get("Editor to use when editing PKGBUILDs")},
{"--editorflags <flags>", gotext.Get("Pass arguments to editor")},
{"--makepkg <file>", gotext.Get("makepkg command to use")},
{"--mflags <flags>", gotext.Get("Pass arguments to makepkg")},
{"--pacman <file>", gotext.Get("pacman command to use")},
{"--git <file>", gotext.Get("git command to use")},
{"--gitflags <flags>", gotext.Get("Pass arguments to git")},
{"--gpg <file>", gotext.Get("gpg command to use")},
{"--gpgflags <flags>", gotext.Get("Pass arguments to gpg")},
{"--config <file>", gotext.Get("pacman.conf file to use")},
{"--makepkgconf <file>", gotext.Get("makepkg.conf file to use")},
{"--nomakepkgconf", gotext.Get("Use the default makepkg.conf")},
{},
{"--requestsplitn <n>", gotext.Get("Max amount of packages to query per AUR request")},
{"--completioninterval <n>", gotext.Get("Time in days to refresh completion cache")},
{"--sortby <field>", gotext.Get("Sort AUR results by a specific field during search")},
{"--searchby <field>", gotext.Get("Search for packages using a specified field")},
{"--answerclean <a>", gotext.Get("Set a predetermined answer for the clean build menu")},
{"--answerdiff <a>", gotext.Get("Set a predetermined answer for the diff menu")},
{"--answeredit <a>", gotext.Get("Set a predetermined answer for the edit pkgbuild menu")},
{"--answerupgrade <a>", gotext.Get("Set a predetermined answer for the upgrade menu")},
{"--noanswerclean", gotext.Get("Unset the answer for the clean build menu")},
{"--noanswerdiff", gotext.Get("Unset the answer for the edit diff menu")},
{"--noansweredit", gotext.Get("Unset the answer for the edit pkgbuild menu")},
{"--noanswerupgrade", gotext.Get("Unset the answer for the upgrade menu")},
{"--cleanmenu", gotext.Get("Give the option to clean build PKGBUILDS")},
{"--diffmenu", gotext.Get("Give the option to show diffs for build files")},
{"--editmenu", gotext.Get("Give the option to edit/view PKGBUILDS")},
{"--askremovemake", gotext.Get("Ask to remove makedepends after install")},
{"--askyesremovemake", gotext.Get("Ask to remove makedepends after install(\"Y\" as default)")},
{"--removemake", gotext.Get("Remove makedepends after install")},
{"--noremovemake", gotext.Get("Don't remove makedepends after install")},
{},
{"--cleanafter", gotext.Get("Remove package sources after successful install")},
{"--keepsrc", gotext.Get("Keep pkg/ and src/ after building packages")},
{"--bottomup", gotext.Get("Shows AUR's packages first and then repository's")},
{"--topdown", gotext.Get("Shows repository's packages first and then AUR's")},
{"--singlelineresults", gotext.Get("List each search result on its own line")},
{"--doublelineresults", gotext.Get("List each search result on two lines, like pacman")},
{},
{"--devel", gotext.Get("Check development packages during sysupgrade")},
{"--rebuild", gotext.Get("Always build target packages")},
{"--rebuildall", gotext.Get("Always build all AUR packages")},
{"--norebuild", gotext.Get("Skip package build if in cache and up to date")},
{"--rebuildtree", gotext.Get("Always build all AUR packages even if installed")},
{"--redownload", gotext.Get("Always download pkgbuilds of targets")},
{"--noredownload", gotext.Get("Skip pkgbuild download if in cache and up to date")},
{"--redownloadall", gotext.Get("Always download pkgbuilds of all AUR packages")},
{"--provides", gotext.Get("Look for matching providers when searching for packages")},
{"--pgpfetch", gotext.Get("Prompt to import PGP keys from PKGBUILDs")},
{"--useask", gotext.Get("Automatically resolve conflicts using pacman's ask flag")},
{},
{"--sudo <file>", gotext.Get("sudo command to use")},
{"--sudoflags <flags>", gotext.Get("Pass arguments to sudo")},
{"--sudoloop", gotext.Get("Loop sudo calls in the background to avoid timeout")},
})
}
func operationUsage(logger *text.Logger, operation string) {
help := findYayOperationHelp(operation)
if help == nil {
return
}
logger.Print(gotext.Get("Usage: %s %s\noptions:\n", help.syntax(), help.targets))
for _, option := range help.options {
logger.Printf(" %-21s %s\n", option.flags, option.description)
}
logger.Println("\n" + gotext.Get("use 'yay {-h --help}' for global options"))
}
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)
}
if run.Cfg.SudoLoop && cmdArgs.NeedRoot(run.Cfg.Mode) {
run.CmdBuilder.SudoLoop()
}
switch cmdArgs.Op {
case "D", "database":
return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
case "F", "files":
return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
case "Q", "query":
return handleQuery(ctx, run, cmdArgs, dbExecutor)
case "R", "remove":
return handleRemove(ctx, run, cmdArgs, run.VCSStore)
case "S", "sync":
return handleSync(ctx, run, cmdArgs, dbExecutor)
case "T", "deptest":
return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
case "U", "upgrade":
return handleUpgrade(ctx, run, cmdArgs)
case "B", "build":
return handleBuild(ctx, run, dbExecutor, cmdArgs)
case "G", "getpkgbuild":
return handleGetpkgbuild(ctx, run, cmdArgs, dbExecutor)
case "P", "show":
return handlePrint(ctx, run, cmdArgs, dbExecutor)
case "Y", "yay":
return handleYay(ctx, run, cmdArgs, run.CmdBuilder,
dbExecutor, run.QueryBuilder)
case "W", "web":
return handleWeb(ctx, run, cmdArgs)
}
return errors.New(gotext.Get("unhandled operation"))
}
// getFilter returns filter function which can keep packages which were only
// explicitly installed or ones installed as dependencies for showing available
// updates or their count.
func getFilter(cmdArgs *parser.Arguments) (upgrade.Filter, error) {
deps, explicit := cmdArgs.ExistsArg("d", "deps"), cmdArgs.ExistsArg("e", "explicit")
switch {
case deps && explicit:
return nil, errors.New(gotext.Get("invalid option: '--deps' and '--explicit' may not be used together"))
case deps:
return func(pkg *upgrade.Upgrade) bool {
return pkg.Reason == alpm.PkgReasonDepend
}, nil
case explicit:
return func(pkg *upgrade.Upgrade) bool {
return pkg.Reason == alpm.PkgReasonExplicit
}, nil
}
return func(pkg *upgrade.Upgrade) bool {
return true
}, nil
}
func handleQuery(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
if cmdArgs.ExistsArg("u", "upgrades") {
filter, err := getFilter(cmdArgs)
if err != nil {
return err
}
return printUpdateList(ctx, run, cmdArgs, dbExecutor,
cmdArgs.ExistsDouble("u", "sysupgrade"), filter)
}
if err := run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm)); err != nil {
if str := err.Error(); strings.Contains(str, "exit status") {
// yay -Qdt should not output anything in case of error
return fmt.Errorf("")
}
return err
}
return nil
}
func handleHelp(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments) error {
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
}
return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
}
func handleVersion(logger *text.Logger) {
logger.Printf("yay v%s - libalpm v%s\n", yayVersion, alpm.Version())
}
func handlePrint(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
switch {
case cmdArgs.ExistsArg("d", "defaultconfig"):
tmpConfig := settings.DefaultConfig(yayVersion)
run.Logger.Printf("%v", tmpConfig)
return nil
case cmdArgs.ExistsArg("g", "currentconfig"):
run.Logger.Printf("%v", run.Cfg)
return nil
case cmdArgs.ExistsArg("w", "news"):
double := cmdArgs.ExistsDouble("w", "news")
quiet := cmdArgs.ExistsArg("q", "quiet")
return news.PrintNewsFeed(ctx, run.HTTPClient, run.Logger,
dbExecutor.LastBuildTime(), run.Cfg.BottomUp, double, quiet)
case cmdArgs.ExistsArg("c", "complete"):
return completion.Show(ctx, run.HTTPClient, dbExecutor,
run.Cfg.AURURL, run.Cfg.CompletionPath, run.Cfg.CompletionInterval, cmdArgs.ExistsDouble("c", "complete"), run.Logger)
case cmdArgs.ExistsArg("s", "stats"):
return localStatistics(ctx, run, dbExecutor)
}
return nil
}
func handleYay(ctx context.Context, run *runtime.Runtime,
cmdArgs *parser.Arguments, cmdBuilder exe.ICmdBuilder,
dbExecutor db.Executor, queryBuilder query.Builder,
) error {
switch {
case cmdArgs.ExistsArg("gendb"):
return createDevelDB(ctx, run, dbExecutor)
case cmdArgs.ExistsDouble("c"):
return cleanDependencies(ctx, run.Cfg, cmdBuilder, cmdArgs, dbExecutor, true)
case cmdArgs.ExistsArg("c", "clean"):
return cleanDependencies(ctx, run.Cfg, cmdBuilder, cmdArgs, dbExecutor, false)
case len(cmdArgs.Targets) > 0:
return displayNumberMenu(ctx, run, cmdArgs.Targets, dbExecutor, queryBuilder, cmdArgs)
}
return nil
}
func handleWeb(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments) error {
switch {
case cmdArgs.ExistsArg("v", "vote"):
return handlePackageVote(ctx, cmdArgs.Targets, run.AURClient, run.Logger,
run.VoteClient, true)
case cmdArgs.ExistsArg("u", "unvote"):
return handlePackageVote(ctx, cmdArgs.Targets, run.AURClient, run.Logger,
run.VoteClient, false)
}
return nil
}
func handleGetpkgbuild(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor download.DBSearcher) error {
if cmdArgs.ExistsArg("p", "print") {
return printPkgbuilds(dbExecutor, run.AURClient,
run.HTTPClient, run.Logger, cmdArgs.Targets, run.Cfg.Mode, run.Cfg.AURURL)
}
return getPkgbuilds(ctx, dbExecutor, run.AURClient, run,
cmdArgs.Targets, cmdArgs.ExistsArg("f", "force"))
}
func handleUpgrade(ctx context.Context,
run *runtime.Runtime, cmdArgs *parser.Arguments,
) error {
return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
}
// -B* options
func handleBuild(ctx context.Context,
run *runtime.Runtime, dbExecutor db.Executor, cmdArgs *parser.Arguments,
) error {
return installLocalPKGBUILD(ctx, run, cmdArgs, dbExecutor)
}
func handleSync(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
targets := cmdArgs.Targets
switch {
case cmdArgs.ExistsArg("s", "search"):
return syncSearch(ctx, targets, dbExecutor, run.QueryBuilder, !cmdArgs.ExistsArg("q", "quiet"))
case cmdArgs.ExistsArg("p", "print", "print-format"):
return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
case cmdArgs.ExistsArg("c", "clean"):
return syncClean(ctx, run, cmdArgs, dbExecutor)
case cmdArgs.ExistsArg("l", "list"):
return syncList(ctx, run, run.HTTPClient, cmdArgs, dbExecutor)
case cmdArgs.ExistsArg("g", "groups"):
return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
case cmdArgs.ExistsArg("i", "info"):
return syncInfo(ctx, run, cmdArgs, targets, dbExecutor)
case cmdArgs.ExistsArg("u", "sysupgrade") || len(cmdArgs.Targets) > 0:
return syncInstall(ctx, run, cmdArgs, dbExecutor)
case cmdArgs.ExistsArg("y", "refresh"):
return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
}
return nil
}
func handleRemove(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, localCache vcs.Store) error {
err := run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
if err == nil {
localCache.RemovePackages(cmdArgs.Targets)
}
return err
}
// NumberMenu presents a CLI for selecting packages to install.
func displayNumberMenu(ctx context.Context, run *runtime.Runtime, pkgS []string, dbExecutor db.Executor,
queryBuilder query.Builder, cmdArgs *parser.Arguments,
) error {
queryBuilder.Execute(ctx, dbExecutor, pkgS)
if err := queryBuilder.Results(dbExecutor, query.NumberMenu); err != nil {
return err
}
if queryBuilder.Len() == 0 {
// no results were found
return nil
}
run.Logger.Infoln(gotext.Get("Packages to install (eg: 1 2 3, 1-3 or ^4)"))
numberBuf, err := run.Logger.GetInput("", false)
if err != nil {
return err
}
include, exclude, _, otherExclude := intrange.ParseNumberMenu(numberBuf)
targets, err := queryBuilder.GetTargets(include, exclude, otherExclude)
if err != nil {
return err
}
// modify the arguments to pass for the install
cmdArgs.Targets = targets
if len(cmdArgs.Targets) == 0 {
run.Logger.Println(gotext.Get(" there is nothing to do"))
return nil
}
return syncInstall(ctx, run, cmdArgs, dbExecutor)
}
func syncList(ctx context.Context, run *runtime.Runtime,
httpClient *http.Client, cmdArgs *parser.Arguments, dbExecutor db.Executor,
) error {
aur := false
for i, v := range slices.Backward(cmdArgs.Targets) {
if v == "aur" && run.Cfg.Mode.AtLeastAUR() {
cmdArgs.Targets = append(cmdArgs.Targets[:i], cmdArgs.Targets[i+1:]...)
aur = true
}
}
if run.Cfg.Mode.AtLeastAUR() && (len(cmdArgs.Targets) == 0 || aur) {
scanner, err := download.GetPackageScanner(ctx, httpClient, run.Cfg.AURURL, run.Logger)
if err != nil {
return err
}
defer scanner.Close()
for scanner.Scan() {
name := scanner.Text()
if cmdArgs.ExistsArg("q", "quiet") {
run.Logger.Println(name)
} else {
run.Logger.Printf("%s %s %s", text.Magenta("aur"), text.Bold(name), text.Bold(text.Green(gotext.Get("unknown-version"))))
if dbExecutor.LocalPackage(name) != nil {
run.Logger.Print(text.Bold(text.Blue(gotext.Get(" [Installed]"))))
}
run.Logger.Println()
}
}
}
if run.Cfg.Mode.AtLeastRepo() && (len(cmdArgs.Targets) != 0 || !aur) {
return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, run.Cfg.Mode, settings.NoConfirm))
}
return nil
}