Fix minor question and locale issues (#1786)

* add missing locales

* use t.Setenv instead of os.Setenv for tests

* locale: present y/n if localisation is not latin. Always accept y/n in every case

* question: use operation info for question

* edge case where localised n is equal to default y

* add tests for basic locales
This commit is contained in:
Jo 2022-08-13 22:56:23 +00:00 committed by GitHub
parent 85934bddea
commit 888fb4b1d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 196 additions and 57 deletions

View file

@ -64,6 +64,7 @@ linters:
- unconvert
- unparam
- unused
- tenv
- varcheck
- whitespace
- wsl

View file

@ -18,7 +18,7 @@ VERSION ?= ${MAJORVERSION}.${MINORVERSION}.${PATCHVERSION}
LOCALEDIR := po
SYSTEMLOCALEPATH := $(PREFIX)/share/locale/
LANGS := de en es eu fr_FR id it_IT ja ko pl_PL pt pt_BR ru_RU sv tr zh_CN
LANGS := de en es eu fr_FR id it_IT ja ko pl_PL pt pt_BR ru_RU sv tr uk zh-Hans zh_CN zh_TW
POTFILE := default.pot
POFILES := $(addprefix $(LOCALEDIR)/,$(addsuffix .po,$(LANGS)))
MOFILES := $(POFILES:.po=.mo)

View file

@ -76,7 +76,7 @@ func syncClean(ctx context.Context, cmdArgs *parser.Arguments, dbExecutor db.Exe
fmt.Println(gotext.Get("\nBuild directory:"), config.BuildDir)
if text.ContinueTask(question, true, settings.NoConfirm) {
if text.ContinueTask(os.Stdin, question, true, settings.NoConfirm) {
if err := cleanAUR(ctx, keepInstalled, keepCurrent, removeAll, dbExecutor); err != nil {
return err
}
@ -86,7 +86,7 @@ func syncClean(ctx context.Context, cmdArgs *parser.Arguments, dbExecutor db.Exe
return nil
}
if text.ContinueTask(gotext.Get("Do you want to remove ALL untracked AUR files?"), true, settings.NoConfirm) {
if text.ContinueTask(os.Stdin, gotext.Get("Do you want to remove ALL untracked AUR files?"), true, settings.NoConfirm) {
return cleanUntracked(ctx)
}

View file

@ -210,7 +210,7 @@ func install(ctx context.Context, cmdArgs *parser.Arguments, dbExecutor db.Execu
case "no":
break
default:
if text.ContinueTask(gotext.Get("Remove make dependencies after install?"), false, settings.NoConfirm) {
if text.ContinueTask(os.Stdin, gotext.Get("Remove make dependencies after install?"), false, settings.NoConfirm) {
defer func() {
err = removeMake(ctx, do)
}()
@ -467,7 +467,7 @@ nextpkg:
fmt.Println()
if !text.ContinueTask(gotext.Get("Try to build them anyway?"), true, settings.NoConfirm) {
if !text.ContinueTask(os.Stdin, gotext.Get("Try to build them anyway?"), true, settings.NoConfirm) {
return nil, &settings.ErrUserAbort{}
}
}

View file

@ -4,6 +4,7 @@ package menus
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
@ -149,7 +150,8 @@ func updatePkgbuildSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, buil
func Diff(ctx context.Context, cmdBuilder exe.ICmdBuilder,
buildDir string, diffMenuOption bool, bases []dep.Base,
installed stringset.StringSet, cloned map[string]bool, noConfirm bool, diffDefaultAnswer string) error {
installed stringset.StringSet, cloned map[string]bool, noConfirm bool, diffDefaultAnswer string,
) error {
if !diffMenuOption {
return nil
}
@ -166,7 +168,7 @@ func Diff(ctx context.Context, cmdBuilder exe.ICmdBuilder,
fmt.Println()
if !text.ContinueTask(gotext.Get("Proceed with install?"), true, false) {
if !text.ContinueTask(os.Stdin, gotext.Get("Proceed with install?"), true, false) {
return settings.ErrUserAbort{}
}

View file

@ -83,7 +83,8 @@ func editor(editorConfig, editorFlags string, noConfirm bool) (editor string, ar
}
func editPkgbuilds(buildDir string, bases []dep.Base, editorConfig,
editorFlags string, srcinfos map[string]*gosrc.Srcinfo, noConfirm bool) error {
editorFlags string, srcinfos map[string]*gosrc.Srcinfo, noConfirm bool,
) error {
pkgbuilds := make([]string, 0, len(bases))
for _, base := range bases {
@ -114,7 +115,8 @@ func editPkgbuilds(buildDir string, bases []dep.Base, editorConfig,
func Edit(editMenuOption bool, buildDir string, bases []dep.Base, editorConfig,
editorFlags string, installed stringset.StringSet, srcinfos map[string]*gosrc.Srcinfo,
noConfirm bool, editDefaultAnswer string) error {
noConfirm bool, editDefaultAnswer string,
) error {
if !editMenuOption {
return nil
}
@ -131,7 +133,7 @@ func Edit(editMenuOption bool, buildDir string, bases []dep.Base, editorConfig,
fmt.Println()
if !text.ContinueTask(gotext.Get("Proceed with install?"), true, false) {
if !text.ContinueTask(os.Stdin, gotext.Get("Proceed with install?"), true, false) {
return settings.ErrUserAbort{}
}

View file

@ -45,7 +45,8 @@ func (set pgpKeySet) get(key string) bool {
// CheckPgpKeys iterates through the keys listed in the PKGBUILDs and if needed,
// asks the user whether yay should try to import them.
func CheckPgpKeys(bases []dep.Base, srcinfos map[string]*gosrc.Srcinfo,
gpgBin, gpgFlags string, noConfirm bool) error {
gpgBin, gpgFlags string, noConfirm bool,
) error {
// Let's check the keys individually, and then we can offer to import
// the problematic ones.
problematic := make(pgpKeySet)
@ -85,7 +86,7 @@ func CheckPgpKeys(bases []dep.Base, srcinfos map[string]*gosrc.Srcinfo,
fmt.Println()
fmt.Println(str)
if text.ContinueTask(gotext.Get("Import?"), true, noConfirm) {
if text.ContinueTask(os.Stdin, gotext.Get("Import?"), true, noConfirm) {
return importKeys(problematic.toSlice(), gpgBin, gpgFlags)
}

View file

@ -18,7 +18,7 @@ func TestNewConfig(t *testing.T) {
err := os.MkdirAll(filepath.Join(configDir, "yay"), 0o755)
assert.NoError(t, err)
os.Setenv("XDG_CONFIG_HOME", configDir)
t.Setenv("XDG_CONFIG_HOME", configDir)
cacheDir := t.TempDir()
@ -50,12 +50,12 @@ func TestNewConfigAURDEST(t *testing.T) {
err := os.MkdirAll(filepath.Join(configDir, "yay"), 0o755)
assert.NoError(t, err)
os.Setenv("XDG_CONFIG_HOME", configDir)
t.Setenv("XDG_CONFIG_HOME", configDir)
cacheDir := t.TempDir()
config := map[string]string{"BuildDir": filepath.Join(cacheDir, "test-other-dir")}
os.Setenv("AURDEST", filepath.Join(cacheDir, "test-build-dir"))
t.Setenv("AURDEST", filepath.Join(cacheDir, "test-build-dir"))
f, err := os.Create(filepath.Join(configDir, "yay", "config.json"))
assert.NoError(t, err)
@ -79,8 +79,6 @@ func TestNewConfigAURDEST(t *testing.T) {
// WHEN setPrivilegeElevator gets called
// THEN sudobin should stay as "sudo" (given sudo exists)
func TestConfiguration_setPrivilegeElevator(t *testing.T) {
oldPath := os.Getenv("PATH")
path := t.TempDir()
doas := filepath.Join(path, "sudo")
@ -92,9 +90,8 @@ func TestConfiguration_setPrivilegeElevator(t *testing.T) {
config.SudoLoop = true
config.SudoFlags = "-v"
os.Setenv("PATH", path)
t.Setenv("PATH", path)
err = config.setPrivilegeElevator()
os.Setenv("PATH", oldPath)
assert.NoError(t, err)
assert.Equal(t, "sudo", config.SudoBin)
@ -107,8 +104,6 @@ func TestConfiguration_setPrivilegeElevator(t *testing.T) {
// WHEN setPrivilegeElevator gets called
// THEN sudobin should be changed to "su"
func TestConfiguration_setPrivilegeElevator_su(t *testing.T) {
oldPath := os.Getenv("PATH")
path := t.TempDir()
doas := filepath.Join(path, "su")
@ -120,9 +115,8 @@ func TestConfiguration_setPrivilegeElevator_su(t *testing.T) {
config.SudoLoop = true
config.SudoFlags = "-v"
os.Setenv("PATH", path)
t.Setenv("PATH", path)
err = config.setPrivilegeElevator()
os.Setenv("PATH", oldPath)
assert.NoError(t, err)
assert.Equal(t, "su", config.SudoBin)
@ -135,15 +129,12 @@ func TestConfiguration_setPrivilegeElevator_su(t *testing.T) {
// WHEN setPrivilegeElevator gets called
// THEN sudobin should be changed to "su"
func TestConfiguration_setPrivilegeElevator_no_path(t *testing.T) {
oldPath := os.Getenv("PATH")
os.Setenv("PATH", "")
t.Setenv("PATH", "")
config := DefaultConfig("test")
config.SudoLoop = true
config.SudoFlags = "-v"
err := config.setPrivilegeElevator()
os.Setenv("PATH", oldPath)
assert.Error(t, err)
assert.Equal(t, "sudo", config.SudoBin)
@ -156,8 +147,6 @@ func TestConfiguration_setPrivilegeElevator_no_path(t *testing.T) {
// WHEN setPrivilegeElevator gets called
// THEN sudobin should be changed to "doas"
func TestConfiguration_setPrivilegeElevator_doas(t *testing.T) {
oldPath := os.Getenv("PATH")
path := t.TempDir()
doas := filepath.Join(path, "doas")
@ -169,9 +158,8 @@ func TestConfiguration_setPrivilegeElevator_doas(t *testing.T) {
config.SudoLoop = true
config.SudoFlags = "-v"
os.Setenv("PATH", path)
t.Setenv("PATH", path)
err = config.setPrivilegeElevator()
os.Setenv("PATH", oldPath)
assert.NoError(t, err)
assert.Equal(t, "doas", config.SudoBin)
assert.Equal(t, "", config.SudoFlags)
@ -183,8 +171,6 @@ func TestConfiguration_setPrivilegeElevator_doas(t *testing.T) {
// WHEN setPrivilegeElevator gets called
// THEN sudobin should be kept as the wrapper
func TestConfiguration_setPrivilegeElevator_custom_script(t *testing.T) {
oldPath := os.Getenv("PATH")
path := t.TempDir()
wrapper := filepath.Join(path, "custom-wrapper")
@ -197,9 +183,8 @@ func TestConfiguration_setPrivilegeElevator_custom_script(t *testing.T) {
config.SudoBin = wrapper
config.SudoFlags = "-v"
os.Setenv("PATH", path)
t.Setenv("PATH", path)
err = config.setPrivilegeElevator()
os.Setenv("PATH", oldPath)
assert.NoError(t, err)
assert.Equal(t, wrapper, config.SudoBin)
@ -212,8 +197,6 @@ func TestConfiguration_setPrivilegeElevator_custom_script(t *testing.T) {
// WHEN setPrivilegeElevator gets called
// THEN sudobin should be changed to "doas"
func TestConfiguration_setPrivilegeElevator_pacman_auth_doas(t *testing.T) {
oldPath := os.Getenv("PATH")
path := t.TempDir()
doas := filepath.Join(path, "doas")
@ -231,10 +214,9 @@ func TestConfiguration_setPrivilegeElevator_pacman_auth_doas(t *testing.T) {
config.SudoLoop = true
config.SudoFlags = "-v"
os.Setenv("PACMAN_AUTH", "doas")
os.Setenv("PATH", path)
t.Setenv("PACMAN_AUTH", "doas")
t.Setenv("PATH", path)
err = config.setPrivilegeElevator()
os.Setenv("PATH", oldPath)
assert.NoError(t, err)
assert.Equal(t, "doas", config.SudoBin)
assert.Equal(t, "", config.SudoFlags)
@ -246,8 +228,6 @@ func TestConfiguration_setPrivilegeElevator_pacman_auth_doas(t *testing.T) {
// WHEN setPrivilegeElevator gets called
// THEN sudobin should be changed to "sudo"
func TestConfiguration_setPrivilegeElevator_pacman_auth_sudo(t *testing.T) {
oldPath := os.Getenv("PATH")
path := t.TempDir()
doas := filepath.Join(path, "doas")
@ -265,10 +245,9 @@ func TestConfiguration_setPrivilegeElevator_pacman_auth_sudo(t *testing.T) {
config.SudoLoop = true
config.SudoFlags = "-v"
os.Setenv("PACMAN_AUTH", "sudo")
os.Setenv("PATH", path)
t.Setenv("PACMAN_AUTH", "sudo")
t.Setenv("PATH", path)
err = config.setPrivilegeElevator()
os.Setenv("PATH", oldPath)
assert.NoError(t, err)
assert.Equal(t, "sudo", config.SudoBin)
assert.Equal(t, "-v", config.SudoFlags)

View file

@ -16,8 +16,8 @@ func Test_getCacheHome(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.Unsetenv("XDG_CACHE_HOME"))
require.NoError(t, os.Unsetenv("HOME"))
require.NoError(t, os.Setenv("SUDO_USER", "test"))
require.NoError(t, os.Setenv("TMPDIR", dir))
t.Setenv("SUDO_USER", "test")
t.Setenv("TMPDIR", dir)
got, err := getCacheHome()
require.NoError(t, err)

View file

@ -2,12 +2,19 @@ package text
import (
"fmt"
"io"
"strings"
"unicode"
"unicode/utf8"
"github.com/leonelquinteros/gotext"
)
const (
yDefault = "y"
nDefault = "n"
)
// SplitDBFromName split apart db/package to db and package.
func SplitDBFromName(pkg string) (db, name string) {
split := strings.SplitN(pkg, "/", 2)
@ -48,31 +55,46 @@ func LessRunes(iRunes, jRunes []rune) bool {
// ContinueTask prompts if user wants to continue task.
// If NoConfirm is set the action will continue without user input.
func ContinueTask(s string, cont, noConfirm bool) bool {
func ContinueTask(input io.Reader, s string, preset, noConfirm bool) bool {
if noConfirm {
return cont
return preset
}
var (
response string
postFix string
n string
y string
yes = gotext.Get("yes")
no = gotext.Get("no")
y = string([]rune(yes)[0]) // nolint
n = string([]rune(no)[0]) // nolint
)
if cont {
postFix = fmt.Sprintf(" [%s/%s] ", strings.ToUpper(y), n)
// Only use localized "y" and "n" if they are latin characters.
if nRune, _ := utf8.DecodeRuneInString(no); unicode.Is(unicode.Latin, nRune) {
n = string(nRune)
} else {
n = nDefault
}
if yRune, _ := utf8.DecodeRuneInString(yes); unicode.Is(unicode.Latin, yRune) {
y = string(yRune)
} else {
y = yDefault
}
if preset { // If default behavior is true, use y as default.
postFix = fmt.Sprintf(" [%s/%s] ", strings.ToUpper(y), n)
} else { // If default behavior is anything else, use n as default.
postFix = fmt.Sprintf(" [%s/%s] ", y, strings.ToUpper(n))
}
Info(Bold(s), Bold(postFix))
OperationInfo(Bold(s), Bold(postFix))
if _, err := fmt.Scanln(&response); err != nil {
return cont
if _, err := fmt.Fscanln(input, &response); err != nil {
return preset
}
return strings.EqualFold(response, yes) || strings.EqualFold(response, y)
return strings.EqualFold(response, yes) ||
strings.EqualFold(response, y) ||
(!strings.EqualFold(yDefault, n) && strings.EqualFold(response, yDefault))
}

View file

@ -1,9 +1,14 @@
package text
import (
"os"
"path"
"strings"
"testing"
"github.com/leonelquinteros/gotext"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLessRunes(t *testing.T) {
@ -39,3 +44,130 @@ func TestLessRunes(t *testing.T) {
})
}
}
func TestContinueTask(t *testing.T) {
t.Parallel()
type args struct {
s string
preset bool
noConfirm bool
input string
}
tests := []struct {
name string
args args
want bool
}{
{name: "noconfirm-true", args: args{s: "", preset: true, noConfirm: true}, want: true},
{name: "noconfirm-false", args: args{s: "", preset: false, noConfirm: true}, want: false},
{name: "noinput-false", args: args{s: "", preset: false, noConfirm: false}, want: false},
{name: "noinput-true", args: args{s: "", preset: true, noConfirm: false}, want: true},
{name: "input-false", args: args{s: "", input: "n", preset: true, noConfirm: false}, want: false},
{name: "input-true", args: args{s: "", input: "y", preset: false, noConfirm: false}, want: true},
{name: "input-false-complete", args: args{s: "", input: "no", preset: true, noConfirm: false}, want: false},
{name: "input-true-complete", args: args{s: "", input: "yes", preset: false, noConfirm: false}, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// create io.Reader with value of input
in := strings.NewReader(tt.args.input)
got := ContinueTask(in, tt.args.s, tt.args.preset, tt.args.noConfirm)
require.Equal(t, tt.want, got)
})
}
}
func TestContinueTaskRU(t *testing.T) {
strCustom := `
msgid "yes"
msgstr "да"
`
// Create Locales directory and files on temp location
tmpDir := t.TempDir()
dirname := path.Join(tmpDir, "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
require.NoError(t, err)
fDefault, err := os.Create(path.Join(dirname, "yay.po"))
require.NoError(t, err)
defer fDefault.Close()
_, err = fDefault.WriteString(strCustom)
require.NoError(t, err)
gotext.Configure(tmpDir, "en_US", "yay")
require.Equal(t, "да", gotext.Get("yes"))
type args struct {
s string
preset bool
noConfirm bool
input string
}
tests := []struct {
name string
args args
want bool
}{
{name: "default input false", args: args{s: "", input: "n", preset: true, noConfirm: false}, want: false},
{name: "default input true", args: args{s: "", input: "y", preset: false, noConfirm: false}, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
in := strings.NewReader(tt.args.input)
got := ContinueTask(in, tt.args.s, tt.args.preset, tt.args.noConfirm)
require.Equal(t, tt.want, got)
})
}
gotext.SetLanguage("")
}
func TestContinueTaskDE(t *testing.T) {
strCustom := `
msgid "yes"
msgstr "ja"
`
// Create Locales directory and files on temp location
tmpDir := t.TempDir()
dirname := path.Join(tmpDir, "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
require.NoError(t, err)
fDefault, err := os.Create(path.Join(dirname, "yay.po"))
require.NoError(t, err)
defer fDefault.Close()
_, err = fDefault.WriteString(strCustom)
require.NoError(t, err)
gotext.Configure(tmpDir, "en_US", "yay")
require.Equal(t, "ja", gotext.Get("yes"))
type args struct {
s string
preset bool
noConfirm bool
input string
}
tests := []struct {
name string
args args
want bool
}{
{name: "default input false", args: args{s: "", input: "n", preset: true, noConfirm: false}, want: false},
{name: "default input true", args: args{s: "", input: "y", preset: false, noConfirm: false}, want: true},
{name: "custom input true", args: args{s: "", input: "j", preset: false, noConfirm: false}, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
in := strings.NewReader(tt.args.input)
got := ContinueTask(in, tt.args.s, tt.args.preset, tt.args.noConfirm)
require.Equal(t, tt.want, got)
})
}
gotext.SetLanguage("")
}