Преглед на файлове

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
Jo преди 2 години
родител
ревизия
888fb4b1d0
променени са 11 файла, в които са добавени 196 реда и са изтрити 57 реда
  1. 1 0
      .golangci.yml
  2. 1 1
      Makefile
  3. 2 2
      clean.go
  4. 2 2
      install.go
  5. 4 2
      pkg/menus/diff_menu.go
  6. 5 3
      pkg/menus/edit_menu.go
  7. 3 2
      pkg/pgp/keys.go
  8. 12 33
      pkg/settings/config_test.go
  9. 2 2
      pkg/settings/dirs_test.go
  10. 32 10
      pkg/text/text.go
  11. 132 0
      pkg/text/text_test.go

+ 1 - 0
.golangci.yml

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

+ 1 - 1
Makefile

@@ -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)

+ 2 - 2
clean.go

@@ -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)
 	}
 

+ 2 - 2
install.go

@@ -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{}
 		}
 	}

+ 4 - 2
pkg/menus/diff_menu.go

@@ -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{}
 	}
 

+ 5 - 3
pkg/menus/edit_menu.go

@@ -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{}
 	}
 

+ 3 - 2
pkg/pgp/keys.go

@@ -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)
 	}
 

+ 12 - 33
pkg/settings/config_test.go

@@ -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)

+ 2 - 2
pkg/settings/dirs_test.go

@@ -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)

+ 32 - 10
pkg/text/text.go

@@ -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))
 }

+ 132 - 0
pkg/text/text_test.go

@@ -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("")
+}