mirror of
				https://github.com/shokinn/hosts-go.git
				synced 2025-11-04 12:38:34 +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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p := tea.NewProgram(tui.NewModel(hostsFile))
 | 
			
		||||
	p := tea.NewProgram(tui.NewModel(hostsFile, "/etc/hosts"))
 | 
			
		||||
	if err := p.Start(); err != nil {
 | 
			
		||||
		log.Fatalf("failed to start TUI: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,17 +40,18 @@ const (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
type Model struct {
 | 
			
		||||
	list   list.Model
 | 
			
		||||
	detail viewport.Model
 | 
			
		||||
	hosts  *core.HostsFile
 | 
			
		||||
	width  int
 | 
			
		||||
	height int
 | 
			
		||||
	mode   Mode
 | 
			
		||||
	focus  pane
 | 
			
		||||
	list      list.Model
 | 
			
		||||
	detail    viewport.Model
 | 
			
		||||
	hosts     *core.HostsFile
 | 
			
		||||
	hostsPath string
 | 
			
		||||
	width     int
 | 
			
		||||
	height    int
 | 
			
		||||
	mode      Mode
 | 
			
		||||
	focus     pane
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewModel constructs the TUI model from a parsed HostsFile.
 | 
			
		||||
func NewModel(hf *core.HostsFile) Model {
 | 
			
		||||
// NewModel constructs the TUI model from a parsed HostsFile and its path.
 | 
			
		||||
func NewModel(hf *core.HostsFile, path string) Model {
 | 
			
		||||
	items := make([]list.Item, len(hf.Entries))
 | 
			
		||||
	for i, e := range hf.Entries {
 | 
			
		||||
		items[i] = entryItem{entry: e}
 | 
			
		||||
| 
						 | 
				
			
			@ -63,11 +64,12 @@ func NewModel(hf *core.HostsFile) Model {
 | 
			
		|||
	l.SetShowPagination(false)
 | 
			
		||||
 | 
			
		||||
	m := Model{
 | 
			
		||||
		list:   l,
 | 
			
		||||
		detail: viewport.New(0, 0),
 | 
			
		||||
		hosts:  hf,
 | 
			
		||||
		mode:   ViewMode,
 | 
			
		||||
		focus:  listPane,
 | 
			
		||||
		list:      l,
 | 
			
		||||
		detail:    viewport.New(0, 0),
 | 
			
		||||
		hosts:     hf,
 | 
			
		||||
		hostsPath: path,
 | 
			
		||||
		mode:      ViewMode,
 | 
			
		||||
		focus:     listPane,
 | 
			
		||||
	}
 | 
			
		||||
	m.refreshDetail()
 | 
			
		||||
	return m
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,10 @@
 | 
			
		|||
package tui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"hosts-go/internal/core"
 | 
			
		||||
 | 
			
		||||
	tea "github.com/charmbracelet/bubbletea"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +28,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 | 
			
		|||
			} else {
 | 
			
		||||
				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 " ":
 | 
			
		||||
			if m.mode == EditMode {
 | 
			
		||||
				if entry := m.SelectedEntry(); entry != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,7 +53,7 @@
 | 
			
		|||
   - 🔄 Entry modification forms with validation
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
   - 🔄 Live validation and formatting preview
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,7 @@
 | 
			
		|||
- [ ] **Permission handling**: Request sudo access when entering edit mode
 | 
			
		||||
- [x] **Entry modification**: Toggle active status
 | 
			
		||||
- [ ] **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
 | 
			
		||||
 | 
			
		||||
### 🌐 Advanced Features (Phase 4)
 | 
			
		||||
| 
						 | 
				
			
			@ -77,10 +77,10 @@
 | 
			
		|||
 | 
			
		||||
### Project Phase: **Phase 2 Complete → Phase 3 (Edit Mode Implementation)**
 | 
			
		||||
- **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
 | 
			
		||||
- **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
 | 
			
		||||
- ✅ **Architecture designed**: Clear technical approach documented
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package tests
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +20,7 @@ func TestModelSelection(t *testing.T) {
 | 
			
		|||
	hf, _, err := core.ParseHostsContent(lines)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	m := tui.NewModel(hf)
 | 
			
		||||
	m := tui.NewModel(hf, "")
 | 
			
		||||
	require.NotNil(t, m.SelectedEntry())
 | 
			
		||||
	assert.Equal(t, "localhost", m.SelectedEntry().Hostname)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +37,7 @@ func TestViewModeStatusBar(t *testing.T) {
 | 
			
		|||
	hf, _, err := core.ParseHostsContent(lines)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	m := tui.NewModel(hf)
 | 
			
		||||
	m := tui.NewModel(hf, "")
 | 
			
		||||
	nm, _ := m.Update(tea.WindowSizeMsg{Width: 80, Height: 20})
 | 
			
		||||
	m = nm.(tui.Model)
 | 
			
		||||
	view := m.View()
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +53,7 @@ func TestPaneSwitching(t *testing.T) {
 | 
			
		|||
	hf, _, err := core.ParseHostsContent(lines)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	m := tui.NewModel(hf)
 | 
			
		||||
	m := tui.NewModel(hf, "")
 | 
			
		||||
	// Switch focus to detail pane
 | 
			
		||||
	nm, _ := m.Update(tea.KeyMsg{Type: tea.KeyTab})
 | 
			
		||||
	m = nm.(tui.Model)
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +70,7 @@ func TestEditModeToggleAndActivation(t *testing.T) {
 | 
			
		|||
	hf, _, err := core.ParseHostsContent(lines)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	m := tui.NewModel(hf)
 | 
			
		||||
	m := tui.NewModel(hf, "")
 | 
			
		||||
 | 
			
		||||
	// enter edit mode
 | 
			
		||||
	nm, _ := m.Update(tea.KeyMsg{Type: tea.KeyCtrlE})
 | 
			
		||||
| 
						 | 
				
			
			@ -85,3 +86,27 @@ func TestEditModeToggleAndActivation(t *testing.T) {
 | 
			
		|||
	view := m.View()
 | 
			
		||||
	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