mirror of
https://github.com/shokinn/hosts-go.git
synced 2025-08-23 16:43:02 +00:00
Merge e813704f81
into e59af1cb02
This commit is contained in:
commit
d82ddad9c9
6 changed files with 60 additions and 23 deletions
|
@ -15,7 +15,7 @@ func main() {
|
||||||
log.Fatalf("failed to parse hosts file: %v", err)
|
log.Fatalf("failed to parse hosts file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := tea.NewProgram(tui.NewModel(hostsFile))
|
p := tea.NewProgram(tui.NewModel(hostsFile, "/etc/hosts"))
|
||||||
if err := p.Start(); err != nil {
|
if err := p.Start(); err != nil {
|
||||||
log.Fatalf("failed to start TUI: %v", err)
|
log.Fatalf("failed to start TUI: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,14 +43,15 @@ type Model struct {
|
||||||
list list.Model
|
list list.Model
|
||||||
detail viewport.Model
|
detail viewport.Model
|
||||||
hosts *core.HostsFile
|
hosts *core.HostsFile
|
||||||
|
hostsPath string
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
mode Mode
|
mode Mode
|
||||||
focus pane
|
focus pane
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewModel constructs the TUI model from a parsed HostsFile.
|
// NewModel constructs the TUI model from a parsed HostsFile and its path.
|
||||||
func NewModel(hf *core.HostsFile) Model {
|
func NewModel(hf *core.HostsFile, path string) Model {
|
||||||
items := make([]list.Item, len(hf.Entries))
|
items := make([]list.Item, len(hf.Entries))
|
||||||
for i, e := range hf.Entries {
|
for i, e := range hf.Entries {
|
||||||
items[i] = entryItem{entry: e}
|
items[i] = entryItem{entry: e}
|
||||||
|
@ -66,6 +67,7 @@ func NewModel(hf *core.HostsFile) Model {
|
||||||
list: l,
|
list: l,
|
||||||
detail: viewport.New(0, 0),
|
detail: viewport.New(0, 0),
|
||||||
hosts: hf,
|
hosts: hf,
|
||||||
|
hostsPath: path,
|
||||||
mode: ViewMode,
|
mode: ViewMode,
|
||||||
focus: listPane,
|
focus: listPane,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"hosts-go/internal/core"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,6 +28,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
} else {
|
} else {
|
||||||
m.mode = ViewMode
|
m.mode = ViewMode
|
||||||
}
|
}
|
||||||
|
case "ctrl+s":
|
||||||
|
if m.mode == EditMode && m.hostsPath != "" {
|
||||||
|
if err := core.WriteHostsFile(m.hostsPath, m.hosts); err != nil {
|
||||||
|
fmt.Println("save failed:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
case " ":
|
case " ":
|
||||||
if m.mode == EditMode {
|
if m.mode == EditMode {
|
||||||
if entry := m.SelectedEntry(); entry != nil {
|
if entry := m.SelectedEntry(); entry != nil {
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
- 🔄 Entry modification forms with validation
|
- 🔄 Entry modification forms with validation
|
||||||
|
|
||||||
2. **File Integration**
|
2. **File Integration**
|
||||||
- 🔄 Connect TUI with existing parser functionality for writes
|
- ✅ Connect TUI with parser to persist changes (Ctrl+S)
|
||||||
- 🔄 Real-time display of actual `/etc/hosts` content
|
- 🔄 Real-time display of actual `/etc/hosts` content
|
||||||
- 🔄 Live validation and formatting preview
|
- 🔄 Live validation and formatting preview
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
- [ ] **Permission handling**: Request sudo access when entering edit mode
|
- [ ] **Permission handling**: Request sudo access when entering edit mode
|
||||||
- [x] **Entry modification**: Toggle active status
|
- [x] **Entry modification**: Toggle active status
|
||||||
- [ ] **Entry modification**: Add, edit, delete operations
|
- [ ] **Entry modification**: Add, edit, delete operations
|
||||||
- [ ] **File writing**: Atomic updates with backup and rollback
|
- [x] **File writing**: Atomic updates with backup and rollback
|
||||||
- [ ] **Input validation**: Real-time validation of IP and hostname inputs
|
- [ ] **Input validation**: Real-time validation of IP and hostname inputs
|
||||||
|
|
||||||
### 🌐 Advanced Features (Phase 4)
|
### 🌐 Advanced Features (Phase 4)
|
||||||
|
@ -77,10 +77,10 @@
|
||||||
|
|
||||||
### Project Phase: **Phase 2 Complete → Phase 3 (Edit Mode Implementation)**
|
### Project Phase: **Phase 2 Complete → Phase 3 (Edit Mode Implementation)**
|
||||||
- **Completion**: ~80% (parser, TUI, and basic edit mode implemented)
|
- **Completion**: ~80% (parser, TUI, and basic edit mode implemented)
|
||||||
- **Active work**: Expand edit mode with file integration and advanced editing
|
- **Active work**: Expand edit mode with advanced editing features
|
||||||
- **Blockers**: None - comprehensive parser foundation with 54 tests completed
|
- **Blockers**: None - comprehensive parser foundation with 54 tests completed
|
||||||
- **Parser status**: Production-ready with all safety features implemented
|
- **Parser status**: Production-ready with all safety features implemented
|
||||||
- **Key bindings updated**: `ctrl+e` for edit mode, spacebar to toggle entries
|
- **Key bindings updated**: `ctrl+e` for edit mode, spacebar to toggle entries, `ctrl+s` to save
|
||||||
|
|
||||||
### Development Readiness
|
### Development Readiness
|
||||||
- ✅ **Architecture designed**: Clear technical approach documented
|
- ✅ **Architecture designed**: Clear technical approach documented
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ func TestModelSelection(t *testing.T) {
|
||||||
hf, _, err := core.ParseHostsContent(lines)
|
hf, _, err := core.ParseHostsContent(lines)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
m := tui.NewModel(hf)
|
m := tui.NewModel(hf, "")
|
||||||
require.NotNil(t, m.SelectedEntry())
|
require.NotNil(t, m.SelectedEntry())
|
||||||
assert.Equal(t, "localhost", m.SelectedEntry().Hostname)
|
assert.Equal(t, "localhost", m.SelectedEntry().Hostname)
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ func TestViewModeStatusBar(t *testing.T) {
|
||||||
hf, _, err := core.ParseHostsContent(lines)
|
hf, _, err := core.ParseHostsContent(lines)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
m := tui.NewModel(hf)
|
m := tui.NewModel(hf, "")
|
||||||
nm, _ := m.Update(tea.WindowSizeMsg{Width: 80, Height: 20})
|
nm, _ := m.Update(tea.WindowSizeMsg{Width: 80, Height: 20})
|
||||||
m = nm.(tui.Model)
|
m = nm.(tui.Model)
|
||||||
view := m.View()
|
view := m.View()
|
||||||
|
@ -52,7 +53,7 @@ func TestPaneSwitching(t *testing.T) {
|
||||||
hf, _, err := core.ParseHostsContent(lines)
|
hf, _, err := core.ParseHostsContent(lines)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
m := tui.NewModel(hf)
|
m := tui.NewModel(hf, "")
|
||||||
// Switch focus to detail pane
|
// Switch focus to detail pane
|
||||||
nm, _ := m.Update(tea.KeyMsg{Type: tea.KeyTab})
|
nm, _ := m.Update(tea.KeyMsg{Type: tea.KeyTab})
|
||||||
m = nm.(tui.Model)
|
m = nm.(tui.Model)
|
||||||
|
@ -69,7 +70,7 @@ func TestEditModeToggleAndActivation(t *testing.T) {
|
||||||
hf, _, err := core.ParseHostsContent(lines)
|
hf, _, err := core.ParseHostsContent(lines)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
m := tui.NewModel(hf)
|
m := tui.NewModel(hf, "")
|
||||||
|
|
||||||
// enter edit mode
|
// enter edit mode
|
||||||
nm, _ := m.Update(tea.KeyMsg{Type: tea.KeyCtrlE})
|
nm, _ := m.Update(tea.KeyMsg{Type: tea.KeyCtrlE})
|
||||||
|
@ -85,3 +86,27 @@ func TestEditModeToggleAndActivation(t *testing.T) {
|
||||||
view := m.View()
|
view := m.View()
|
||||||
assert.Contains(t, view, "EDIT MODE")
|
assert.Contains(t, view, "EDIT MODE")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSaveWritesToFile(t *testing.T) {
|
||||||
|
content := "127.0.0.1 localhost\n192.168.1.10 example.com\n"
|
||||||
|
tmp, err := os.CreateTemp(t.TempDir(), "hosts")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = tmp.WriteString(content)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, tmp.Close())
|
||||||
|
|
||||||
|
hf, _, err := core.ParseHostsFile(tmp.Name())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
m := tui.NewModel(hf, tmp.Name())
|
||||||
|
|
||||||
|
nm, _ := m.Update(tea.KeyMsg{Type: tea.KeyCtrlE})
|
||||||
|
m = nm.(tui.Model)
|
||||||
|
nm, _ = m.Update(tea.KeyMsg{Type: tea.KeySpace})
|
||||||
|
m = nm.(tui.Model)
|
||||||
|
_, _ = m.Update(tea.KeyMsg{Type: tea.KeyCtrlS})
|
||||||
|
|
||||||
|
saved, _, err := core.ParseHostsFile(tmp.Name())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, saved.Entries[0].Active)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue