mirror of
				https://github.com/shokinn/hosts-go.git
				synced 2025-11-04 12:38:34 +00:00 
			
		
		
		
	feat(parser): Implement hosts file parser with intelligent formatting
- Added `internal/core/parser.go` for parsing hosts files, including: - Support for standard entries (IPv4, IPv6, multiple aliases, inline comments) - Handling of comments and disabled entries - Error recovery for malformed lines with warnings - Intelligent formatting with adaptive spacing and column alignment - Backup and atomic write operations for file safety test(parser): Add comprehensive tests for hosts file parsing - Created `tests/parser_test.go` with 54 test cases covering: - Standard entries and comments - Malformed lines and whitespace variations - Round-trip parsing to ensure format preservation - Backup functionality for hosts files docs(progress): Update project progress and next steps - Mark Phase 1 as complete and outline tasks for Phase 2 (TUI implementation) - Highlight completed features and testing coverage
This commit is contained in:
		
							parent
							
								
									d66ec51ebd
								
							
						
					
					
						commit
						b81f11f711
					
				
					 10 changed files with 1303 additions and 210 deletions
				
			
		| 
						 | 
				
			
			@ -3,81 +3,135 @@ package main
 | 
			
		|||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"hosts-go/internal/core"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	fmt.Println("hosts-go - Foundation Implementation")
 | 
			
		||||
	fmt.Println("===================================")
 | 
			
		||||
	fmt.Println("hosts-go - Phase 1: Core Functionality (Parser)")
 | 
			
		||||
	fmt.Println("===============================================")
 | 
			
		||||
 | 
			
		||||
	// Create a new hosts file
 | 
			
		||||
	hostsFile := core.NewHostsFile()
 | 
			
		||||
	// Demonstrate hosts file parsing with sample content
 | 
			
		||||
	sampleHostsContent := `# Sample hosts file content
 | 
			
		||||
127.0.0.1	localhost	# Local loopback
 | 
			
		||||
::1		ip6-localhost	# IPv6 loopback
 | 
			
		||||
192.168.1.100	dev.example.com	www.dev.example.com	api.dev.example.com	# Development server
 | 
			
		||||
# 10.0.0.50	staging.example.com	# Disabled staging server
 | 
			
		||||
203.0.113.10	prod.example.com	# Production server
 | 
			
		||||
 | 
			
		||||
	// Add some example entries to demonstrate the foundation
 | 
			
		||||
	entry1, err := core.NewHostEntry("127.0.0.1", "localhost")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Failed to create entry: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	entry1.Comment = "Local loopback"
 | 
			
		||||
# Another comment
 | 
			
		||||
::ffff:192.168.1.200	test.example.com	# Test server`
 | 
			
		||||
 | 
			
		||||
	entry2, err := core.NewHostEntry("192.168.1.100", "dev.example.com")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Failed to create entry: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	entry2.AddAlias("www.dev.example.com")
 | 
			
		||||
	entry2.AddAlias("api.dev.example.com")
 | 
			
		||||
	entry2.Comment = "Development server"
 | 
			
		||||
 | 
			
		||||
	entry3, err := core.NewHostEntry("10.0.0.50", "staging.example.com")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Failed to create entry: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	entry3.Active = false // Inactive entry
 | 
			
		||||
	entry3.Comment = "Staging server (disabled)"
 | 
			
		||||
 | 
			
		||||
	// Add entries to hosts file
 | 
			
		||||
	hostsFile.AddEntry(entry1)
 | 
			
		||||
	hostsFile.AddEntry(entry2)
 | 
			
		||||
	hostsFile.AddEntry(entry3)
 | 
			
		||||
 | 
			
		||||
	// Demonstrate the foundation functionality
 | 
			
		||||
	fmt.Printf("Total entries: %d\n", len(hostsFile.Entries))
 | 
			
		||||
	fmt.Printf("Active entries: %d\n", len(hostsFile.ActiveEntries()))
 | 
			
		||||
	fmt.Println("Sample hosts file content:")
 | 
			
		||||
	fmt.Println(strings.Repeat("-", 50))
 | 
			
		||||
	fmt.Println(sampleHostsContent)
 | 
			
		||||
	fmt.Println(strings.Repeat("-", 50))
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
 | 
			
		||||
	fmt.Println("All entries:")
 | 
			
		||||
	// Parse the sample content
 | 
			
		||||
	lines := strings.Split(sampleHostsContent, "\n")
 | 
			
		||||
	hostsFile, warnings, err := core.ParseHostsContent(lines)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Failed to parse hosts content: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Display parsing results
 | 
			
		||||
	fmt.Printf("✅ Parsing successful!\n")
 | 
			
		||||
	fmt.Printf("   Total entries: %d\n", len(hostsFile.Entries))
 | 
			
		||||
	fmt.Printf("   Active entries: %d\n", len(hostsFile.ActiveEntries()))
 | 
			
		||||
	fmt.Printf("   Standalone comments: %d\n", len(hostsFile.Comments))
 | 
			
		||||
	fmt.Printf("   Warnings: %d\n", len(warnings))
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
 | 
			
		||||
	// Show warnings if any
 | 
			
		||||
	if len(warnings) > 0 {
 | 
			
		||||
		fmt.Println("Parsing warnings:")
 | 
			
		||||
		for _, warning := range warnings {
 | 
			
		||||
			fmt.Printf("  Line %d: %s\n", warning.Line, warning.Message)
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Show standalone comments
 | 
			
		||||
	if len(hostsFile.Comments) > 0 {
 | 
			
		||||
		fmt.Println("Standalone comments found:")
 | 
			
		||||
		for i, comment := range hostsFile.Comments {
 | 
			
		||||
			fmt.Printf("%d. %s\n", i+1, comment)
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Show parsed entries
 | 
			
		||||
	fmt.Println("Parsed entries:")
 | 
			
		||||
	for i, entry := range hostsFile.Entries {
 | 
			
		||||
		fmt.Printf("%d. %s\n", i+1, entry.String())
 | 
			
		||||
		status := "✅ Active"
 | 
			
		||||
		if !entry.Active {
 | 
			
		||||
			status = "❌ Disabled"
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%d. [%s] %s -> %s", i+1, status, entry.IP, entry.Hostname)
 | 
			
		||||
		if len(entry.Aliases) > 0 {
 | 
			
		||||
			fmt.Printf(" (aliases: %s)", strings.Join(entry.Aliases, ", "))
 | 
			
		||||
		}
 | 
			
		||||
		if entry.Comment != "" {
 | 
			
		||||
			fmt.Printf(" # %s", entry.Comment)
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println()
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Active entries only:")
 | 
			
		||||
	for i, entry := range hostsFile.ActiveEntries() {
 | 
			
		||||
		fmt.Printf("%d. %s\n", i+1, entry.String())
 | 
			
		||||
	// Demonstrate intelligent formatting
 | 
			
		||||
	fmt.Println("Intelligent formatting output:")
 | 
			
		||||
	fmt.Println(strings.Repeat("-", 50))
 | 
			
		||||
	formattedLines := core.FormatHostsFile(hostsFile)
 | 
			
		||||
	for _, line := range formattedLines {
 | 
			
		||||
		fmt.Println(line)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(strings.Repeat("-", 50))
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
 | 
			
		||||
	// Demonstrate formatting style detection
 | 
			
		||||
	fmt.Println("Formatting style detection:")
 | 
			
		||||
	style := core.DetectFormattingStyle(lines)
 | 
			
		||||
	if style.UseTabs {
 | 
			
		||||
		fmt.Printf("  Detected style: Tabs\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Printf("  Detected style: Spaces (%d per tab)\n", style.SpacesPerTab)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Printf("  Column widths: IP=%d, Host=%d\n", style.IPWidth, style.HostWidth)
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
 | 
			
		||||
	// Demonstrate search functionality
 | 
			
		||||
	fmt.Println("Search demonstrations:")
 | 
			
		||||
	if found := hostsFile.FindEntry("localhost"); found != nil {
 | 
			
		||||
		fmt.Printf("Found 'localhost': %s\n", found.String())
 | 
			
		||||
		fmt.Printf("✅ Found 'localhost': %s -> %s\n", found.IP, found.Hostname)
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if found := hostsFile.FindEntry("www.dev.example.com"); found != nil {
 | 
			
		||||
		fmt.Printf("Found 'www.dev.example.com' (alias): %s\n", found.String())
 | 
			
		||||
		fmt.Printf("✅ Found alias 'www.dev.example.com': %s -> %s\n", found.IP, found.Hostname)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if found := hostsFile.FindEntry("staging.example.com"); found != nil {
 | 
			
		||||
		status := "active"
 | 
			
		||||
		if !found.Active {
 | 
			
		||||
			status = "disabled"
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("✅ Found 'staging.example.com': %s -> %s (%s)\n", found.IP, found.Hostname, status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if found := hostsFile.FindEntry("notfound.com"); found == nil {
 | 
			
		||||
		fmt.Println("'notfound.com' not found (as expected)")
 | 
			
		||||
		fmt.Printf("❌ 'notfound.com' not found (as expected)\n")
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
 | 
			
		||||
	fmt.Println("🎉 Phase 1 Complete: Core Functionality (Parser)")
 | 
			
		||||
	fmt.Println("✅ Hosts file parsing with format preservation")
 | 
			
		||||
	fmt.Println("✅ Comment and disabled entry handling")
 | 
			
		||||
	fmt.Println("✅ Intelligent formatting with column alignment")
 | 
			
		||||
	fmt.Println("✅ Malformed line handling with warnings")
 | 
			
		||||
	fmt.Println("✅ Round-trip parsing (parse → format → parse)")
 | 
			
		||||
	fmt.Println("✅ Backup functionality")
 | 
			
		||||
	fmt.Println("✅ Search and entry management")
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
	fmt.Println("Foundation implementation complete!")
 | 
			
		||||
	fmt.Println("✅ Core data models working")
 | 
			
		||||
	fmt.Println("✅ Validation system working") 
 | 
			
		||||
	fmt.Println("✅ Host entry management working")
 | 
			
		||||
	fmt.Println("✅ Search and filtering working")
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
	fmt.Println("Next steps: Implement hosts file parser and TUI components")
 | 
			
		||||
	fmt.Println("Ready for Phase 2: TUI Implementation!")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue