fix(config): remove remote configuration
Signed-off-by: brice <b@bnema.dev>
- commit
- 3f6dcc6bf606e4c68c7b7becefa9758e89050a41
- author
- brice · · 2026-07-30 08:33 UTC
- parent
- a806195
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +2 -328
M docs/configuration.md +1 -13
| @@ -75,21 +75,9 @@ code.toggle-floating-pane = FLT | |||
| 75 | 75 | code.rename-session = RNS | |
| 76 | 76 | code.rename-tab = RNT | |
| 77 | 77 | code.detach = DET | |
| 78 | - | ||
| 79 | - | [remote] | |
| 80 | - | enabled = true | |
| 81 | - | remember = true | |
| 82 | 78 | ``` | |
| 83 | 79 | ||
| 84 | - | Invalid values log a warning and resolve that setting to its default on both initial load and reload. A leftover `[remote] hosts = ...` assignment fails closed as unsupported; host lists are not stored in config. | |
| 85 | - | ||
| 86 | - | ## Remote | |
| 87 | - | ||
| 88 | - | The optional `[remote]` section accepts only scalar booleans: `enabled` and `remember` (TOML-style `true`/`false`). Both default to `true`. | |
| 89 | - | ||
| 90 | - | `enabled = false` is a kill-switch for unified remote listing and host management. Direct `vev attach user@host[:session]` still works. `remember = false` stops learning hosts on successful attach. | |
| 91 | - | ||
| 92 | - | Host lists are not part of config. A `hosts` key under `[remote]` is a hard load error. Unrecognized remote keys log an `unknown key` warning. An unrecognized section header logs an `unknown section` warning and ignores its keys until the next section header. | |
| 80 | + | Invalid values log a warning and resolve that setting to its default on both initial load and reload. | |
| 93 | 81 | ||
| 94 | 82 | ## Logs and durable state | |
| 95 | 83 | ||
| @@ -75,21 +75,9 @@ code.toggle-floating-pane = FLT | |||||
| 75 | code.rename-session = RNS | 75 | code.rename-session = RNS | ||
| 76 | code.rename-tab = RNT | 76 | code.rename-tab = RNT | ||
| 77 | code.detach = DET | 77 | code.detach = DET | ||
| 78 | - | ||||
| 79 | - | [remote] | |||
| 80 | - | enabled = true | |||
| 81 | - | remember = true | |||
| 82 | ``` | 78 | ``` | ||
| 83 | 79 | ||||
| 84 | - | Invalid values log a warning and resolve that setting to its default on both initial load and reload. A leftover `[remote] hosts = ...` assignment fails closed as unsupported; host lists are not stored in config. | 80 | + | Invalid values log a warning and resolve that setting to its default on both initial load and reload. |
| 85 | - | ||||
| 86 | - | ## Remote | |||
| 87 | - | ||||
| 88 | - | The optional `[remote]` section accepts only scalar booleans: `enabled` and `remember` (TOML-style `true`/`false`). Both default to `true`. | |||
| 89 | - | ||||
| 90 | - | `enabled = false` is a kill-switch for unified remote listing and host management. Direct `vev attach user@host[:session]` still works. `remember = false` stops learning hosts on successful attach. | |||
| 91 | - | ||||
| 92 | - | Host lists are not part of config. A `hosts` key under `[remote]` is a hard load error. Unrecognized remote keys log an `unknown key` warning. An unrecognized section header logs an `unknown section` warning and ignores its keys until the next section header. | |||
| 93 | 81 | ||||
| 94 | ## Logs and durable state | 82 | ## Logs and durable state | ||
| 95 | 83 | ||||
M internal/adapters/config/config.go +1 -31
| @@ -21,14 +21,11 @@ const pollInterval = 2 * time.Second | |||
| 21 | 21 | var ( | |
| 22 | 22 | processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`) | |
| 23 | 23 | percentagePattern = regexp.MustCompile(`^[0-9]{1,3}%$`) | |
| 24 | - | sectionPattern = regexp.MustCompile(`^\[([^\]]+)\]$`) | |
| 25 | 24 | ) | |
| 26 | 25 | ||
| 27 | 26 | // Parse reads vev's flat action = value config format. Duplicate action keys | |
| 28 | 27 | // are accepted with a warning; the last value wins while first-seen action order | |
| 29 | - | // is preserved for binding conflict resolution. An optional [remote] section | |
| 30 | - | // accepts enabled/remember with TOML-style true/false values. A leftover hosts | |
| 31 | - | // assignment fails closed as unsupported. | |
| 28 | + | // is preserved for binding conflict resolution. | |
| 32 | 29 | func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |
| 33 | 30 | cfg := domain.Defaults() | |
| 34 | 31 | var warnings []domain.Warning | |
| @@ -38,8 +35,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||
| 38 | 35 | seenPaletteKeys := make(map[string]bool) | |
| 39 | 36 | seenNavKeys := make(map[string]bool) | |
| 40 | 37 | seenTabsKeys := make(map[string]bool) | |
| 41 | - | seenRemoteKeys := make(map[string]bool) | |
| 42 | - | section := "" | |
| 43 | 38 | ||
| 44 | 39 | scanner := bufio.NewScanner(r) | |
| 45 | 40 | lineNo := 0 | |
| @@ -54,17 +49,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||
| 54 | 49 | continue | |
| 55 | 50 | } | |
| 56 | 51 | ||
| 57 | - | if matches := sectionPattern.FindStringSubmatch(line); matches != nil { | |
| 58 | - | name := matches[1] | |
| 59 | - | if name == "remote" { | |
| 60 | - | section = "remote" | |
| 61 | - | } else { | |
| 62 | - | section = "unknown:" + name | |
| 63 | - | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("unknown section %q", name)}) | |
| 64 | - | } | |
| 65 | - | continue | |
| 66 | - | } | |
| 67 | - | ||
| 68 | 52 | key, value, ok := strings.Cut(line, "=") | |
| 69 | 53 | if !ok { | |
| 70 | 54 | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: "missing '='"}) | |
| @@ -77,20 +61,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||
| 77 | 61 | continue | |
| 78 | 62 | } | |
| 79 | 63 | ||
| 80 | - | if strings.HasPrefix(section, "unknown:") { | |
| 81 | - | continue | |
| 82 | - | } | |
| 83 | - | if section == "remote" { | |
| 84 | - | var remoteWarnings []domain.Warning | |
| 85 | - | var remoteErr error | |
| 86 | - | cfg.Remote, remoteWarnings, remoteErr = parseRemoteKey(cfg.Remote, seenRemoteKeys, key, value, lineNo) | |
| 87 | - | warnings = append(warnings, remoteWarnings...) | |
| 88 | - | if remoteErr != nil { | |
| 89 | - | return cfg, warnings, remoteErr | |
| 90 | - | } | |
| 91 | - | continue | |
| 92 | - | } | |
| 93 | - | ||
| 94 | 64 | switch { | |
| 95 | 65 | case key == "theme": | |
| 96 | 66 | mode, ok := parseTheme(value) | |
| @@ -21,14 +21,11 @@ const pollInterval = 2 * time.Second | |||||
| 21 | var ( | 21 | var ( | ||
| 22 | processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`) | 22 | processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`) | ||
| 23 | percentagePattern = regexp.MustCompile(`^[0-9]{1,3}%$`) | 23 | percentagePattern = regexp.MustCompile(`^[0-9]{1,3}%$`) | ||
| 24 | - | sectionPattern = regexp.MustCompile(`^\[([^\]]+)\]$`) | |||
| 25 | ) | 24 | ) | ||
| 26 | 25 | ||||
| 27 | // Parse reads vev's flat action = value config format. Duplicate action keys | 26 | // Parse reads vev's flat action = value config format. Duplicate action keys | ||
| 28 | // are accepted with a warning; the last value wins while first-seen action order | 27 | // are accepted with a warning; the last value wins while first-seen action order | ||
| 29 | - | // is preserved for binding conflict resolution. An optional [remote] section | 28 | + | // is preserved for binding conflict resolution. |
| 30 | - | // accepts enabled/remember with TOML-style true/false values. A leftover hosts | |||
| 31 | - | // assignment fails closed as unsupported. | |||
| 32 | func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | 29 | func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | ||
| 33 | cfg := domain.Defaults() | 30 | cfg := domain.Defaults() | ||
| 34 | var warnings []domain.Warning | 31 | var warnings []domain.Warning | ||
| @@ -38,8 +35,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||||
| 38 | seenPaletteKeys := make(map[string]bool) | 35 | seenPaletteKeys := make(map[string]bool) | ||
| 39 | seenNavKeys := make(map[string]bool) | 36 | seenNavKeys := make(map[string]bool) | ||
| 40 | seenTabsKeys := make(map[string]bool) | 37 | seenTabsKeys := make(map[string]bool) | ||
| 41 | - | seenRemoteKeys := make(map[string]bool) | |||
| 42 | - | section := "" | |||
| 43 | 38 | ||||
| 44 | scanner := bufio.NewScanner(r) | 39 | scanner := bufio.NewScanner(r) | ||
| 45 | lineNo := 0 | 40 | lineNo := 0 | ||
| @@ -54,17 +49,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||||
| 54 | continue | 49 | continue | ||
| 55 | } | 50 | } | ||
| 56 | 51 | ||||
| 57 | - | if matches := sectionPattern.FindStringSubmatch(line); matches != nil { | |||
| 58 | - | name := matches[1] | |||
| 59 | - | if name == "remote" { | |||
| 60 | - | section = "remote" | |||
| 61 | - | } else { | |||
| 62 | - | section = "unknown:" + name | |||
| 63 | - | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("unknown section %q", name)}) | |||
| 64 | - | } | |||
| 65 | - | continue | |||
| 66 | - | } | |||
| 67 | - | ||||
| 68 | key, value, ok := strings.Cut(line, "=") | 52 | key, value, ok := strings.Cut(line, "=") | ||
| 69 | if !ok { | 53 | if !ok { | ||
| 70 | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: "missing '='"}) | 54 | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: "missing '='"}) | ||
| @@ -77,20 +61,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||||
| 77 | continue | 61 | continue | ||
| 78 | } | 62 | } | ||
| 79 | 63 | ||||
| 80 | - | if strings.HasPrefix(section, "unknown:") { | |||
| 81 | - | continue | |||
| 82 | - | } | |||
| 83 | - | if section == "remote" { | |||
| 84 | - | var remoteWarnings []domain.Warning | |||
| 85 | - | var remoteErr error | |||
| 86 | - | cfg.Remote, remoteWarnings, remoteErr = parseRemoteKey(cfg.Remote, seenRemoteKeys, key, value, lineNo) | |||
| 87 | - | warnings = append(warnings, remoteWarnings...) | |||
| 88 | - | if remoteErr != nil { | |||
| 89 | - | return cfg, warnings, remoteErr | |||
| 90 | - | } | |||
| 91 | - | continue | |||
| 92 | - | } | |||
| 93 | - | ||||
| 94 | switch { | 64 | switch { | ||
| 95 | case key == "theme": | 65 | case key == "theme": | ||
| 96 | mode, ok := parseTheme(value) | 66 | mode, ok := parseTheme(value) | ||
M internal/adapters/config/config_test.go +0 -15
| @@ -29,7 +29,6 @@ func TestParse(t *testing.T) { | |||
| 29 | 29 | input string | |
| 30 | 30 | want domain.Config | |
| 31 | 31 | wantWarnings []domain.Warning | |
| 32 | - | checkRemote bool | |
| 33 | 32 | }{ | |
| 34 | 33 | { | |
| 35 | 34 | name: "comments and inline comments", | |
| @@ -52,17 +51,6 @@ func TestParse(t *testing.T) { | |||
| 52 | 51 | Codes: map[string]string{"detach": "dt"}, | |
| 53 | 52 | }, | |
| 54 | 53 | }, | |
| 55 | - | { | |
| 56 | - | name: "remote can disable enabled and remember", | |
| 57 | - | input: "[remote]\nenabled = false\nremember = false\n", | |
| 58 | - | want: domain.Config{ | |
| 59 | - | Theme: domain.ThemeAuto, | |
| 60 | - | BindingEntries: []domain.ConfigEntry{}, | |
| 61 | - | Codes: map[string]string{}, | |
| 62 | - | Remote: domain.RemoteConfig{Enabled: false, Remember: false}, | |
| 63 | - | }, | |
| 64 | - | checkRemote: true, | |
| 65 | - | }, | |
| 66 | 54 | { | |
| 67 | 55 | name: "invalid theme warns and keeps default", | |
| 68 | 56 | input: "theme = blue\n", | |
| @@ -370,9 +358,6 @@ func TestParse(t *testing.T) { | |||
| 370 | 358 | if !tt.want.Palette.AnchorSet { | |
| 371 | 359 | tt.want.Palette = domain.Defaults().Palette | |
| 372 | 360 | } | |
| 373 | - | if !tt.checkRemote { | |
| 374 | - | tt.want.Remote = domain.Defaults().Remote | |
| 375 | - | } | |
| 376 | 361 | got, warnings, err := Parse(strings.NewReader(tt.input)) | |
| 377 | 362 | if err != nil { | |
| 378 | 363 | t.Fatalf("Parse() error = %v", err) | |
| @@ -29,7 +29,6 @@ func TestParse(t *testing.T) { | |||||
| 29 | input string | 29 | input string | ||
| 30 | want domain.Config | 30 | want domain.Config | ||
| 31 | wantWarnings []domain.Warning | 31 | wantWarnings []domain.Warning | ||
| 32 | - | checkRemote bool | |||
| 33 | }{ | 32 | }{ | ||
| 34 | { | 33 | { | ||
| 35 | name: "comments and inline comments", | 34 | name: "comments and inline comments", | ||
| @@ -52,17 +51,6 @@ func TestParse(t *testing.T) { | |||||
| 52 | Codes: map[string]string{"detach": "dt"}, | 51 | Codes: map[string]string{"detach": "dt"}, | ||
| 53 | }, | 52 | }, | ||
| 54 | }, | 53 | }, | ||
| 55 | - | { | |||
| 56 | - | name: "remote can disable enabled and remember", | |||
| 57 | - | input: "[remote]\nenabled = false\nremember = false\n", | |||
| 58 | - | want: domain.Config{ | |||
| 59 | - | Theme: domain.ThemeAuto, | |||
| 60 | - | BindingEntries: []domain.ConfigEntry{}, | |||
| 61 | - | Codes: map[string]string{}, | |||
| 62 | - | Remote: domain.RemoteConfig{Enabled: false, Remember: false}, | |||
| 63 | - | }, | |||
| 64 | - | checkRemote: true, | |||
| 65 | - | }, | |||
| 66 | { | 54 | { | ||
| 67 | name: "invalid theme warns and keeps default", | 55 | name: "invalid theme warns and keeps default", | ||
| 68 | input: "theme = blue\n", | 56 | input: "theme = blue\n", | ||
| @@ -370,9 +358,6 @@ func TestParse(t *testing.T) { | |||||
| 370 | if !tt.want.Palette.AnchorSet { | 358 | if !tt.want.Palette.AnchorSet { | ||
| 371 | tt.want.Palette = domain.Defaults().Palette | 359 | tt.want.Palette = domain.Defaults().Palette | ||
| 372 | } | 360 | } | ||
| 373 | - | if !tt.checkRemote { | |||
| 374 | - | tt.want.Remote = domain.Defaults().Remote | |||
| 375 | - | } | |||
| 376 | got, warnings, err := Parse(strings.NewReader(tt.input)) | 361 | got, warnings, err := Parse(strings.NewReader(tt.input)) | ||
| 377 | if err != nil { | 362 | if err != nil { | ||
| 378 | t.Fatalf("Parse() error = %v", err) | 363 | t.Fatalf("Parse() error = %v", err) | ||
D internal/adapters/config/remote.go +0 -50
| @@ -1,50 +0,0 @@ | |||
| 1 | - | package config | |
| 2 | - | ||
| 3 | - | import ( | |
| 4 | - | "errors" | |
| 5 | - | "fmt" | |
| 6 | - | "strings" | |
| 7 | - | ||
| 8 | - | "github.com/bnema/vev/internal/domain" | |
| 9 | - | ) | |
| 10 | - | ||
| 11 | - | // ErrUnsupportedRemoteHosts indicates a legacy remote hosts assignment. | |
| 12 | - | var ErrUnsupportedRemoteHosts = errors.New("unsupported remote hosts assignment") | |
| 13 | - | ||
| 14 | - | func parseRemoteKey(remote domain.RemoteConfig, seen map[string]bool, key, value string, lineNo int) (domain.RemoteConfig, []domain.Warning, error) { | |
| 15 | - | var warnings []domain.Warning | |
| 16 | - | switch key { | |
| 17 | - | case "enabled": | |
| 18 | - | warnings = warnDuplicateKey(warnings, seen, key, lineNo) | |
| 19 | - | on, ok := parseTrueFalse(value) | |
| 20 | - | if !ok { | |
| 21 | - | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid enabled %q", value)}) | |
| 22 | - | return remote, warnings, nil | |
| 23 | - | } | |
| 24 | - | remote.Enabled = on | |
| 25 | - | case "remember": | |
| 26 | - | warnings = warnDuplicateKey(warnings, seen, key, lineNo) | |
| 27 | - | on, ok := parseTrueFalse(value) | |
| 28 | - | if !ok { | |
| 29 | - | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid remember %q", value)}) | |
| 30 | - | return remote, warnings, nil | |
| 31 | - | } | |
| 32 | - | remote.Remember = on | |
| 33 | - | case "hosts": | |
| 34 | - | return remote, warnings, fmt.Errorf("line %d: %w", lineNo, ErrUnsupportedRemoteHosts) | |
| 35 | - | default: | |
| 36 | - | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("unknown key %q", key)}) | |
| 37 | - | } | |
| 38 | - | return remote, warnings, nil | |
| 39 | - | } | |
| 40 | - | ||
| 41 | - | func parseTrueFalse(value string) (bool, bool) { | |
| 42 | - | switch strings.TrimSpace(value) { | |
| 43 | - | case "true": | |
| 44 | - | return true, true | |
| 45 | - | case "false": | |
| 46 | - | return false, true | |
| 47 | - | default: | |
| 48 | - | return false, false | |
| 49 | - | } | |
| 50 | - | } | |
| @@ -1,50 +0,0 @@ | |||||
| 1 | - | package config | |||
| 2 | - | ||||
| 3 | - | import ( | |||
| 4 | - | "errors" | |||
| 5 | - | "fmt" | |||
| 6 | - | "strings" | |||
| 7 | - | ||||
| 8 | - | "github.com/bnema/vev/internal/domain" | |||
| 9 | - | ) | |||
| 10 | - | ||||
| 11 | - | // ErrUnsupportedRemoteHosts indicates a legacy remote hosts assignment. | |||
| 12 | - | var ErrUnsupportedRemoteHosts = errors.New("unsupported remote hosts assignment") | |||
| 13 | - | ||||
| 14 | - | func parseRemoteKey(remote domain.RemoteConfig, seen map[string]bool, key, value string, lineNo int) (domain.RemoteConfig, []domain.Warning, error) { | |||
| 15 | - | var warnings []domain.Warning | |||
| 16 | - | switch key { | |||
| 17 | - | case "enabled": | |||
| 18 | - | warnings = warnDuplicateKey(warnings, seen, key, lineNo) | |||
| 19 | - | on, ok := parseTrueFalse(value) | |||
| 20 | - | if !ok { | |||
| 21 | - | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid enabled %q", value)}) | |||
| 22 | - | return remote, warnings, nil | |||
| 23 | - | } | |||
| 24 | - | remote.Enabled = on | |||
| 25 | - | case "remember": | |||
| 26 | - | warnings = warnDuplicateKey(warnings, seen, key, lineNo) | |||
| 27 | - | on, ok := parseTrueFalse(value) | |||
| 28 | - | if !ok { | |||
| 29 | - | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid remember %q", value)}) | |||
| 30 | - | return remote, warnings, nil | |||
| 31 | - | } | |||
| 32 | - | remote.Remember = on | |||
| 33 | - | case "hosts": | |||
| 34 | - | return remote, warnings, fmt.Errorf("line %d: %w", lineNo, ErrUnsupportedRemoteHosts) | |||
| 35 | - | default: | |||
| 36 | - | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("unknown key %q", key)}) | |||
| 37 | - | } | |||
| 38 | - | return remote, warnings, nil | |||
| 39 | - | } | |||
| 40 | - | ||||
| 41 | - | func parseTrueFalse(value string) (bool, bool) { | |||
| 42 | - | switch strings.TrimSpace(value) { | |||
| 43 | - | case "true": | |||
| 44 | - | return true, true | |||
| 45 | - | case "false": | |||
| 46 | - | return false, true | |||
| 47 | - | default: | |||
| 48 | - | return false, false | |||
| 49 | - | } | |||
| 50 | - | } | |||
D internal/adapters/config/remote_test.go +0 -195
| @@ -1,195 +0,0 @@ | |||
| 1 | - | package config | |
| 2 | - | ||
| 3 | - | import ( | |
| 4 | - | "strings" | |
| 5 | - | "testing" | |
| 6 | - | ||
| 7 | - | "github.com/bnema/vev/internal/domain" | |
| 8 | - | "github.com/stretchr/testify/require" | |
| 9 | - | ) | |
| 10 | - | ||
| 11 | - | func TestParseRemote(t *testing.T) { | |
| 12 | - | t.Parallel() | |
| 13 | - | ||
| 14 | - | tests := []struct { | |
| 15 | - | name string | |
| 16 | - | input string | |
| 17 | - | want domain.RemoteConfig | |
| 18 | - | wantErr string | |
| 19 | - | wantErrIs error | |
| 20 | - | wantWarnings []domain.Warning | |
| 21 | - | wantTheme domain.ThemeMode | |
| 22 | - | checkTheme bool | |
| 23 | - | wantBindingEntries []domain.ConfigEntry | |
| 24 | - | checkBindings bool | |
| 25 | - | }{ | |
| 26 | - | { | |
| 27 | - | name: "defaults when section absent", | |
| 28 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |
| 29 | - | }, | |
| 30 | - | { | |
| 31 | - | name: "complete remote block", | |
| 32 | - | input: `[remote] | |
| 33 | - | enabled = true | |
| 34 | - | remember = true | |
| 35 | - | `, | |
| 36 | - | want: domain.RemoteConfig{ | |
| 37 | - | Enabled: true, | |
| 38 | - | Remember: true, | |
| 39 | - | }, | |
| 40 | - | }, | |
| 41 | - | { | |
| 42 | - | name: "enabled false", | |
| 43 | - | input: `[remote] | |
| 44 | - | enabled = false | |
| 45 | - | `, | |
| 46 | - | want: domain.RemoteConfig{Enabled: false, Remember: true}, | |
| 47 | - | }, | |
| 48 | - | { | |
| 49 | - | name: "remember false", | |
| 50 | - | input: `[remote] | |
| 51 | - | remember = false | |
| 52 | - | `, | |
| 53 | - | want: domain.RemoteConfig{Enabled: true, Remember: false}, | |
| 54 | - | }, | |
| 55 | - | { | |
| 56 | - | name: "legacy on off rejected and defaults retained", | |
| 57 | - | input: `[remote] | |
| 58 | - | enabled = off | |
| 59 | - | remember = on | |
| 60 | - | `, | |
| 61 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |
| 62 | - | wantWarnings: []domain.Warning{ | |
| 63 | - | {Line: 2, Msg: `invalid enabled "off"`}, | |
| 64 | - | {Line: 3, Msg: `invalid remember "on"`}, | |
| 65 | - | }, | |
| 66 | - | }, | |
| 67 | - | { | |
| 68 | - | name: "duplicate keys warn and last valid wins", | |
| 69 | - | input: `[remote] | |
| 70 | - | enabled = false | |
| 71 | - | enabled = true | |
| 72 | - | remember = false | |
| 73 | - | remember = true | |
| 74 | - | `, | |
| 75 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |
| 76 | - | wantWarnings: []domain.Warning{ | |
| 77 | - | {Line: 3, Msg: `duplicate key "enabled"`}, | |
| 78 | - | {Line: 5, Msg: `duplicate key "remember"`}, | |
| 79 | - | }, | |
| 80 | - | }, | |
| 81 | - | { | |
| 82 | - | name: "invalid booleans warn and retain defaults", | |
| 83 | - | input: `[remote] | |
| 84 | - | enabled = yes | |
| 85 | - | remember = True | |
| 86 | - | `, | |
| 87 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |
| 88 | - | wantWarnings: []domain.Warning{ | |
| 89 | - | {Line: 2, Msg: `invalid enabled "yes"`}, | |
| 90 | - | {Line: 3, Msg: `invalid remember "True"`}, | |
| 91 | - | }, | |
| 92 | - | }, | |
| 93 | - | { | |
| 94 | - | name: "hosts assignment fails closed", | |
| 95 | - | input: `[remote] | |
| 96 | - | hosts = ["arch", "build@mule"] | |
| 97 | - | `, | |
| 98 | - | wantErr: `line 2: unsupported remote hosts assignment`, | |
| 99 | - | wantErrIs: ErrUnsupportedRemoteHosts, | |
| 100 | - | wantWarnings: nil, | |
| 101 | - | }, | |
| 102 | - | { | |
| 103 | - | name: "empty hosts array fails closed", | |
| 104 | - | input: `[remote] | |
| 105 | - | hosts = [] | |
| 106 | - | `, | |
| 107 | - | wantErr: `line 2: unsupported remote hosts assignment`, | |
| 108 | - | wantErrIs: ErrUnsupportedRemoteHosts, | |
| 109 | - | wantWarnings: nil, | |
| 110 | - | }, | |
| 111 | - | { | |
| 112 | - | name: "malformed hosts assignment fails closed", | |
| 113 | - | input: `[remote] | |
| 114 | - | hosts = not-an-array | |
| 115 | - | `, | |
| 116 | - | wantErr: `line 2: unsupported remote hosts assignment`, | |
| 117 | - | wantErrIs: ErrUnsupportedRemoteHosts, | |
| 118 | - | wantWarnings: nil, | |
| 119 | - | }, | |
| 120 | - | { | |
| 121 | - | name: "hosts cannot fall through as a binding", | |
| 122 | - | input: `theme = light | |
| 123 | - | [remote] | |
| 124 | - | hosts = ["arch"] | |
| 125 | - | new-tab = alt+t | |
| 126 | - | `, | |
| 127 | - | wantErr: `line 3: unsupported remote hosts assignment`, | |
| 128 | - | wantErrIs: ErrUnsupportedRemoteHosts, | |
| 129 | - | wantWarnings: nil, | |
| 130 | - | }, | |
| 131 | - | { | |
| 132 | - | name: "unknown remote key warns", | |
| 133 | - | input: `[remote] | |
| 134 | - | proxy = true | |
| 135 | - | enabled = false | |
| 136 | - | `, | |
| 137 | - | want: domain.RemoteConfig{Enabled: false, Remember: true}, | |
| 138 | - | wantWarnings: []domain.Warning{ | |
| 139 | - | {Line: 2, Msg: `unknown key "proxy"`}, | |
| 140 | - | }, | |
| 141 | - | }, | |
| 142 | - | { | |
| 143 | - | name: "unknown section warns and ignores its keys", | |
| 144 | - | input: `theme = dark | |
| 145 | - | [other] | |
| 146 | - | enabled = false | |
| 147 | - | [remote] | |
| 148 | - | enabled = false | |
| 149 | - | `, | |
| 150 | - | want: domain.RemoteConfig{Enabled: false, Remember: true}, | |
| 151 | - | wantWarnings: []domain.Warning{ | |
| 152 | - | {Line: 2, Msg: `unknown section "other"`}, | |
| 153 | - | }, | |
| 154 | - | wantTheme: domain.ThemeDark, | |
| 155 | - | checkTheme: true, | |
| 156 | - | }, | |
| 157 | - | { | |
| 158 | - | name: "flat keys preserved outside remote section", | |
| 159 | - | input: `theme = light | |
| 160 | - | new-tab = alt+t | |
| 161 | - | [remote] | |
| 162 | - | enabled = true | |
| 163 | - | `, | |
| 164 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |
| 165 | - | wantTheme: domain.ThemeLight, | |
| 166 | - | checkTheme: true, | |
| 167 | - | wantBindingEntries: []domain.ConfigEntry{{Key: "new-tab", Value: "alt+t"}}, | |
| 168 | - | checkBindings: true, | |
| 169 | - | }, | |
| 170 | - | } | |
| 171 | - | ||
| 172 | - | for _, tt := range tests { | |
| 173 | - | t.Run(tt.name, func(t *testing.T) { | |
| 174 | - | t.Parallel() | |
| 175 | - | ||
| 176 | - | cfg, warnings, err := Parse(strings.NewReader(tt.input)) | |
| 177 | - | if tt.wantErr != "" { | |
| 178 | - | require.EqualError(t, err, tt.wantErr) | |
| 179 | - | require.ErrorIs(t, err, tt.wantErrIs) | |
| 180 | - | require.Equal(t, tt.wantWarnings, warnings) | |
| 181 | - | require.Empty(t, cfg.BindingEntries) | |
| 182 | - | return | |
| 183 | - | } | |
| 184 | - | require.NoError(t, err) | |
| 185 | - | require.Equal(t, tt.want, cfg.Remote) | |
| 186 | - | require.Equal(t, tt.wantWarnings, warnings) | |
| 187 | - | if tt.checkTheme { | |
| 188 | - | require.Equal(t, tt.wantTheme, cfg.Theme) | |
| 189 | - | } | |
| 190 | - | if tt.checkBindings { | |
| 191 | - | require.Equal(t, tt.wantBindingEntries, cfg.BindingEntries) | |
| 192 | - | } | |
| 193 | - | }) | |
| 194 | - | } | |
| 195 | - | } | |
| @@ -1,195 +0,0 @@ | |||||
| 1 | - | package config | |||
| 2 | - | ||||
| 3 | - | import ( | |||
| 4 | - | "strings" | |||
| 5 | - | "testing" | |||
| 6 | - | ||||
| 7 | - | "github.com/bnema/vev/internal/domain" | |||
| 8 | - | "github.com/stretchr/testify/require" | |||
| 9 | - | ) | |||
| 10 | - | ||||
| 11 | - | func TestParseRemote(t *testing.T) { | |||
| 12 | - | t.Parallel() | |||
| 13 | - | ||||
| 14 | - | tests := []struct { | |||
| 15 | - | name string | |||
| 16 | - | input string | |||
| 17 | - | want domain.RemoteConfig | |||
| 18 | - | wantErr string | |||
| 19 | - | wantErrIs error | |||
| 20 | - | wantWarnings []domain.Warning | |||
| 21 | - | wantTheme domain.ThemeMode | |||
| 22 | - | checkTheme bool | |||
| 23 | - | wantBindingEntries []domain.ConfigEntry | |||
| 24 | - | checkBindings bool | |||
| 25 | - | }{ | |||
| 26 | - | { | |||
| 27 | - | name: "defaults when section absent", | |||
| 28 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |||
| 29 | - | }, | |||
| 30 | - | { | |||
| 31 | - | name: "complete remote block", | |||
| 32 | - | input: `[remote] | |||
| 33 | - | enabled = true | |||
| 34 | - | remember = true | |||
| 35 | - | `, | |||
| 36 | - | want: domain.RemoteConfig{ | |||
| 37 | - | Enabled: true, | |||
| 38 | - | Remember: true, | |||
| 39 | - | }, | |||
| 40 | - | }, | |||
| 41 | - | { | |||
| 42 | - | name: "enabled false", | |||
| 43 | - | input: `[remote] | |||
| 44 | - | enabled = false | |||
| 45 | - | `, | |||
| 46 | - | want: domain.RemoteConfig{Enabled: false, Remember: true}, | |||
| 47 | - | }, | |||
| 48 | - | { | |||
| 49 | - | name: "remember false", | |||
| 50 | - | input: `[remote] | |||
| 51 | - | remember = false | |||
| 52 | - | `, | |||
| 53 | - | want: domain.RemoteConfig{Enabled: true, Remember: false}, | |||
| 54 | - | }, | |||
| 55 | - | { | |||
| 56 | - | name: "legacy on off rejected and defaults retained", | |||
| 57 | - | input: `[remote] | |||
| 58 | - | enabled = off | |||
| 59 | - | remember = on | |||
| 60 | - | `, | |||
| 61 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |||
| 62 | - | wantWarnings: []domain.Warning{ | |||
| 63 | - | {Line: 2, Msg: `invalid enabled "off"`}, | |||
| 64 | - | {Line: 3, Msg: `invalid remember "on"`}, | |||
| 65 | - | }, | |||
| 66 | - | }, | |||
| 67 | - | { | |||
| 68 | - | name: "duplicate keys warn and last valid wins", | |||
| 69 | - | input: `[remote] | |||
| 70 | - | enabled = false | |||
| 71 | - | enabled = true | |||
| 72 | - | remember = false | |||
| 73 | - | remember = true | |||
| 74 | - | `, | |||
| 75 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |||
| 76 | - | wantWarnings: []domain.Warning{ | |||
| 77 | - | {Line: 3, Msg: `duplicate key "enabled"`}, | |||
| 78 | - | {Line: 5, Msg: `duplicate key "remember"`}, | |||
| 79 | - | }, | |||
| 80 | - | }, | |||
| 81 | - | { | |||
| 82 | - | name: "invalid booleans warn and retain defaults", | |||
| 83 | - | input: `[remote] | |||
| 84 | - | enabled = yes | |||
| 85 | - | remember = True | |||
| 86 | - | `, | |||
| 87 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |||
| 88 | - | wantWarnings: []domain.Warning{ | |||
| 89 | - | {Line: 2, Msg: `invalid enabled "yes"`}, | |||
| 90 | - | {Line: 3, Msg: `invalid remember "True"`}, | |||
| 91 | - | }, | |||
| 92 | - | }, | |||
| 93 | - | { | |||
| 94 | - | name: "hosts assignment fails closed", | |||
| 95 | - | input: `[remote] | |||
| 96 | - | hosts = ["arch", "build@mule"] | |||
| 97 | - | `, | |||
| 98 | - | wantErr: `line 2: unsupported remote hosts assignment`, | |||
| 99 | - | wantErrIs: ErrUnsupportedRemoteHosts, | |||
| 100 | - | wantWarnings: nil, | |||
| 101 | - | }, | |||
| 102 | - | { | |||
| 103 | - | name: "empty hosts array fails closed", | |||
| 104 | - | input: `[remote] | |||
| 105 | - | hosts = [] | |||
| 106 | - | `, | |||
| 107 | - | wantErr: `line 2: unsupported remote hosts assignment`, | |||
| 108 | - | wantErrIs: ErrUnsupportedRemoteHosts, | |||
| 109 | - | wantWarnings: nil, | |||
| 110 | - | }, | |||
| 111 | - | { | |||
| 112 | - | name: "malformed hosts assignment fails closed", | |||
| 113 | - | input: `[remote] | |||
| 114 | - | hosts = not-an-array | |||
| 115 | - | `, | |||
| 116 | - | wantErr: `line 2: unsupported remote hosts assignment`, | |||
| 117 | - | wantErrIs: ErrUnsupportedRemoteHosts, | |||
| 118 | - | wantWarnings: nil, | |||
| 119 | - | }, | |||
| 120 | - | { | |||
| 121 | - | name: "hosts cannot fall through as a binding", | |||
| 122 | - | input: `theme = light | |||
| 123 | - | [remote] | |||
| 124 | - | hosts = ["arch"] | |||
| 125 | - | new-tab = alt+t | |||
| 126 | - | `, | |||
| 127 | - | wantErr: `line 3: unsupported remote hosts assignment`, | |||
| 128 | - | wantErrIs: ErrUnsupportedRemoteHosts, | |||
| 129 | - | wantWarnings: nil, | |||
| 130 | - | }, | |||
| 131 | - | { | |||
| 132 | - | name: "unknown remote key warns", | |||
| 133 | - | input: `[remote] | |||
| 134 | - | proxy = true | |||
| 135 | - | enabled = false | |||
| 136 | - | `, | |||
| 137 | - | want: domain.RemoteConfig{Enabled: false, Remember: true}, | |||
| 138 | - | wantWarnings: []domain.Warning{ | |||
| 139 | - | {Line: 2, Msg: `unknown key "proxy"`}, | |||
| 140 | - | }, | |||
| 141 | - | }, | |||
| 142 | - | { | |||
| 143 | - | name: "unknown section warns and ignores its keys", | |||
| 144 | - | input: `theme = dark | |||
| 145 | - | [other] | |||
| 146 | - | enabled = false | |||
| 147 | - | [remote] | |||
| 148 | - | enabled = false | |||
| 149 | - | `, | |||
| 150 | - | want: domain.RemoteConfig{Enabled: false, Remember: true}, | |||
| 151 | - | wantWarnings: []domain.Warning{ | |||
| 152 | - | {Line: 2, Msg: `unknown section "other"`}, | |||
| 153 | - | }, | |||
| 154 | - | wantTheme: domain.ThemeDark, | |||
| 155 | - | checkTheme: true, | |||
| 156 | - | }, | |||
| 157 | - | { | |||
| 158 | - | name: "flat keys preserved outside remote section", | |||
| 159 | - | input: `theme = light | |||
| 160 | - | new-tab = alt+t | |||
| 161 | - | [remote] | |||
| 162 | - | enabled = true | |||
| 163 | - | `, | |||
| 164 | - | want: domain.RemoteConfig{Enabled: true, Remember: true}, | |||
| 165 | - | wantTheme: domain.ThemeLight, | |||
| 166 | - | checkTheme: true, | |||
| 167 | - | wantBindingEntries: []domain.ConfigEntry{{Key: "new-tab", Value: "alt+t"}}, | |||
| 168 | - | checkBindings: true, | |||
| 169 | - | }, | |||
| 170 | - | } | |||
| 171 | - | ||||
| 172 | - | for _, tt := range tests { | |||
| 173 | - | t.Run(tt.name, func(t *testing.T) { | |||
| 174 | - | t.Parallel() | |||
| 175 | - | ||||
| 176 | - | cfg, warnings, err := Parse(strings.NewReader(tt.input)) | |||
| 177 | - | if tt.wantErr != "" { | |||
| 178 | - | require.EqualError(t, err, tt.wantErr) | |||
| 179 | - | require.ErrorIs(t, err, tt.wantErrIs) | |||
| 180 | - | require.Equal(t, tt.wantWarnings, warnings) | |||
| 181 | - | require.Empty(t, cfg.BindingEntries) | |||
| 182 | - | return | |||
| 183 | - | } | |||
| 184 | - | require.NoError(t, err) | |||
| 185 | - | require.Equal(t, tt.want, cfg.Remote) | |||
| 186 | - | require.Equal(t, tt.wantWarnings, warnings) | |||
| 187 | - | if tt.checkTheme { | |||
| 188 | - | require.Equal(t, tt.wantTheme, cfg.Theme) | |||
| 189 | - | } | |||
| 190 | - | if tt.checkBindings { | |||
| 191 | - | require.Equal(t, tt.wantBindingEntries, cfg.BindingEntries) | |||
| 192 | - | } | |||
| 193 | - | }) | |||
| 194 | - | } | |||
| 195 | - | } | |||
M internal/domain/config.go +0 -5
| @@ -98,7 +98,6 @@ type Config struct { | |||
| 98 | 98 | Floating FloatingConfig | |
| 99 | 99 | Nav NavConfig | |
| 100 | 100 | Tabs TabsConfig | |
| 101 | - | Remote RemoteConfig | |
| 102 | 101 | } | |
| 103 | 102 | ||
| 104 | 103 | // Warning describes a non-fatal config problem. Parsers and reloaders should | |
| @@ -136,9 +135,5 @@ func Defaults() Config { | |||
| 136 | 135 | Tabs: TabsConfig{ | |
| 137 | 136 | TerminalTitle: true, | |
| 138 | 137 | }, | |
| 139 | - | Remote: RemoteConfig{ | |
| 140 | - | Enabled: true, | |
| 141 | - | Remember: true, | |
| 142 | - | }, | |
| 143 | 138 | } | |
| 144 | 139 | } | |
| @@ -98,7 +98,6 @@ type Config struct { | |||||
| 98 | Floating FloatingConfig | 98 | Floating FloatingConfig | ||
| 99 | Nav NavConfig | 99 | Nav NavConfig | ||
| 100 | Tabs TabsConfig | 100 | Tabs TabsConfig | ||
| 101 | - | Remote RemoteConfig | |||
| 102 | } | 101 | } | ||
| 103 | 102 | ||||
| 104 | // Warning describes a non-fatal config problem. Parsers and reloaders should | 103 | // Warning describes a non-fatal config problem. Parsers and reloaders should | ||
| @@ -136,9 +135,5 @@ func Defaults() Config { | |||||
| 136 | Tabs: TabsConfig{ | 135 | Tabs: TabsConfig{ | ||
| 137 | TerminalTitle: true, | 136 | TerminalTitle: true, | ||
| 138 | }, | 137 | }, | ||
| 139 | - | Remote: RemoteConfig{ | |||
| 140 | - | Enabled: true, | |||
| 141 | - | Remember: true, | |||
| 142 | - | }, | |||
| 143 | } | 138 | } | ||
| 144 | } | 139 | } | ||
M internal/domain/remote_host.go +0 -6
| @@ -6,12 +6,6 @@ import ( | |||
| 6 | 6 | "unicode" | |
| 7 | 7 | ) | |
| 8 | 8 | ||
| 9 | - | // RemoteConfig contains user-configurable remote scalar settings. | |
| 10 | - | type RemoteConfig struct { | |
| 11 | - | Enabled bool | |
| 12 | - | Remember bool | |
| 13 | - | } | |
| 14 | - | ||
| 15 | 9 | // RemoteHost is a merged remote host entry with its source markers. | |
| 16 | 10 | type RemoteHost struct { | |
| 17 | 11 | Target string | |
| @@ -6,12 +6,6 @@ import ( | |||||
| 6 | "unicode" | 6 | "unicode" | ||
| 7 | ) | 7 | ) | ||
| 8 | 8 | ||||
| 9 | - | // RemoteConfig contains user-configurable remote scalar settings. | |||
| 10 | - | type RemoteConfig struct { | |||
| 11 | - | Enabled bool | |||
| 12 | - | Remember bool | |||
| 13 | - | } | |||
| 14 | - | ||||
| 15 | // RemoteHost is a merged remote host entry with its source markers. | 9 | // RemoteHost is a merged remote host entry with its source markers. | ||
| 16 | type RemoteHost struct { | 10 | type RemoteHost struct { | ||
| 17 | Target string | 11 | Target string | ||
M internal/domain/remote_host_test.go +0 -13
| @@ -71,16 +71,3 @@ func TestRemoteHostUniqueTargets(t *testing.T) { | |||
| 71 | 71 | }) | |
| 72 | 72 | } | |
| 73 | 73 | } | |
| 74 | - | ||
| 75 | - | func TestRemoteHostConfigDefaults(t *testing.T) { | |
| 76 | - | cfg := Defaults() | |
| 77 | - | if !cfg.Remote.Enabled { | |
| 78 | - | t.Fatal("Defaults().Remote.Enabled = false, want true") | |
| 79 | - | } | |
| 80 | - | if !cfg.Remote.Remember { | |
| 81 | - | t.Fatal("Defaults().Remote.Remember = false, want true") | |
| 82 | - | } | |
| 83 | - | if cfg.Remote != (RemoteConfig{Enabled: true, Remember: true}) { | |
| 84 | - | t.Fatalf("Defaults().Remote = %#v, want Enabled=true Remember=true only", cfg.Remote) | |
| 85 | - | } | |
| 86 | - | } | |
| @@ -71,16 +71,3 @@ func TestRemoteHostUniqueTargets(t *testing.T) { | |||||
| 71 | }) | 71 | }) | ||
| 72 | } | 72 | } | ||
| 73 | } | 73 | } | ||
| 74 | - | ||||
| 75 | - | func TestRemoteHostConfigDefaults(t *testing.T) { | |||
| 76 | - | cfg := Defaults() | |||
| 77 | - | if !cfg.Remote.Enabled { | |||
| 78 | - | t.Fatal("Defaults().Remote.Enabled = false, want true") | |||
| 79 | - | } | |||
| 80 | - | if !cfg.Remote.Remember { | |||
| 81 | - | t.Fatal("Defaults().Remote.Remember = false, want true") | |||
| 82 | - | } | |||
| 83 | - | if cfg.Remote != (RemoteConfig{Enabled: true, Remember: true}) { | |||
| 84 | - | t.Fatalf("Defaults().Remote = %#v, want Enabled=true Remember=true only", cfg.Remote) | |||
| 85 | - | } | |||
| 86 | - | } | |||