diff --git a/commands.go b/commands.go index 4a320f7..a152379 100644 --- a/commands.go +++ b/commands.go @@ -40,19 +40,6 @@ func MaybeLoadHostFile(c *cli.Context) *Hostfile { return hostsfile } -// ShowEnabled turns a boolean into a string (On) or (Off) -func ShowEnabled(on bool) string { - if on { - return "(On)" - } - return "(Off)" -} - -// ShowHostname turns a Hostname into a string for display -func ShowHostname(hostname Hostname) string { - return fmt.Sprintf("%s -> %s %s", hostname.Domain, hostname.Ip, ShowEnabled(hostname.Enabled)) -} - // StrPadRight adds spaces to the right of a string until it reaches l length. // If the input string is already that long, do nothing. func StrPadRight(s string, l int) string { @@ -78,7 +65,7 @@ func Add(c *cli.Context) { if c.Bool("n") { fmt.Println(hostsfile.Format()) } else { - MaybePrintln(c, fmt.Sprintf("Added %s", ShowHostname(*hostname))) + MaybePrintln(c, fmt.Sprintf("Added %s", hostname.FormatHuman())) hostsfile.Save() } } else { @@ -150,7 +137,7 @@ func Ls(c *cli.Context) { if dlen > maxdomain { maxdomain = dlen } - ilen := len(hostname.Ip) + ilen := len(hostname.IP) if ilen > maxip { maxip = ilen } diff --git a/hostlist.go b/hostlist.go index 8db7f35..b5e9218 100644 --- a/hostlist.go +++ b/hostlist.go @@ -11,11 +11,11 @@ import ( // passed a value not 4 or 6. var ErrInvalidVersionArg = errors.New("Version argument must be 4 or 6") -// Hostlist is a collection of Hostnames. When in a Hostlist, hostnames must +// Hostlist is a collection of Hostnames. When in a Hostlist, Hostnames must // follow some rules: -// - Hostlist may contain IPv4 AND IPv6 (collectively, "IP version") hostnames. +// - Hostlist may contain IPv4 AND IPv6 (collectively, "IP version") Hostnames. // - Names are only allowed to overlap if IP version is different. -// - Adding a hostname for an existing name will replace the old one. +// - Adding a Hostname for an existing name will replace the old one. // See docs for the Sort and Add for more details. type Hostlist []*Hostname @@ -24,12 +24,12 @@ func NewHostlist() *Hostlist { return &Hostlist{} } -// Len returns the number of hostnames in the list, part of sort.Interface +// Len returns the number of Hostnames in the list, part of sort.Interface func (h Hostlist) Len() int { return len(h) } -// Less determines the sort order of two hostnames, part of sort.Interface +// Less determines the sort order of two Hostnames, part of sort.Interface func (h Hostlist) Less(i, j int) bool { // Sort 127.0.0.1, 127.0.1.1 and "localhost" at the top if h[i].Domain == "localhost" { @@ -40,18 +40,18 @@ func (h Hostlist) Less(i, j int) bool { } // Sort IPv4 before IPv6 - if h[i].Ipv6 && !h[j].Ipv6 { + if h[i].IPv6 && !h[j].IPv6 { return false } - if !h[i].Ipv6 && h[j].Ipv6 { + if !h[i].IPv6 && h[j].IPv6 { return true } // Compare the the IP addresses (byte array) - for c := range h[i].Ip { - if h[i].Ip[c] < h[j].Ip[c] { + for c := range h[i].IP { + if h[i].IP[c] < h[j].IP[c] { return true - } else if h[i].Ip[c] > h[j].Ip[c] { + } else if h[i].IP[c] > h[j].IP[c] { return false } } @@ -88,7 +88,7 @@ func (h Hostlist) Less(i, j int) bool { return false } -// Swap changes the position of two hostnames, part of sort.Interface +// Swap changes the position of two Hostnames, part of sort.Interface func (h Hostlist) Swap(i, j int) { h[i], h[j] = h[j], h[i] } @@ -125,7 +125,7 @@ func (h *Hostlist) ContainsDomain(domain string) bool { // ContainsIP returns true if a Hostname in this Hostlist matches IP func (h *Hostlist) ContainsIP(IP net.IP) bool { for _, hostname := range *h { - if hostname.EqualIp(IP) { + if hostname.EqualIP(IP) { return true } } @@ -142,10 +142,10 @@ func (h *Hostlist) Add(host *Hostname) error { for _, found := range *h { if found.Equal(host) { return fmt.Errorf("Duplicate hostname entry for %s -> %s", - host.Domain, host.Ip) - } else if found.Domain == host.Domain && found.Ipv6 == host.Ipv6 { + host.Domain, host.IP) + } else if found.Domain == host.Domain && found.IPv6 == host.IPv6 { return fmt.Errorf("Conflicting hostname entries for %s -> %s and -> %s", - host.Domain, host.Ip, found.Ip) + host.Domain, host.IP, found.IP) } } *h = append(*h, host) @@ -171,7 +171,7 @@ func (h *Hostlist) IndexOfDomainV(domain string, version int) int { panic(ErrInvalidVersionArg) } for index, hostname := range *h { - if hostname.Domain == domain && hostname.Ipv6 == (version == 6) { + if hostname.Domain == domain && hostname.IPv6 == (version == 6) { return index } } @@ -186,7 +186,7 @@ func (h *Hostlist) Remove(index int) { } } -// RemoveDomain removes both Ipv4 and Ipv6 Hostname entries matching domain. +// RemoveDomain removes both IPv4 and IPv6 Hostname entries matching domain. func (h *Hostlist) RemoveDomain(domain string) { h.Remove(h.IndexOfDomainV(domain, 4)) h.Remove(h.IndexOfDomainV(domain, 6)) @@ -217,7 +217,7 @@ func (h *Hostlist) EnableV(domain string, version int) { panic(ErrInvalidVersionArg) } for _, hostname := range *h { - if hostname.Domain == domain && hostname.Ipv6 == (version == 6) { + if hostname.Domain == domain && hostname.IPv6 == (version == 6) { hostname.Enabled = true } } @@ -239,7 +239,7 @@ func (h *Hostlist) DisableV(domain string, version int) { panic(ErrInvalidVersionArg) } for _, hostname := range *h { - if hostname.Domain == domain && hostname.Ipv6 == (version == 6) { + if hostname.Domain == domain && hostname.IPv6 == (version == 6) { hostname.Enabled = false } } @@ -249,9 +249,9 @@ func (h *Hostlist) DisableV(domain string, version int) { // into a string suitable for use as an /etc/hosts file. // Sorting uses the following logic: // 1. List is sorted by IP address -// 2. Commented items are left in place +// 2. Commented items are sorted displayed // 3. 127.* appears at the top of the list (so boot resolvers don't break) -// 4. When present, localhost will always appear first in the domain list +// 4. When present, "localhost" will always appear first in the domain list func (h *Hostlist) Format() string { h.Sort() out := "" diff --git a/hostname.go b/hostname.go index 3e0a003..4d49fa0 100644 --- a/hostname.go +++ b/hostname.go @@ -7,56 +7,84 @@ import ( "strings" ) -var ipv4_pattern = regexp.MustCompile(`^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`) -var ipv6_pattern = regexp.MustCompile(`^[a-z0-9:]+$`) +var ipv4Pattern = regexp.MustCompile(`^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`) +var ipv6Pattern = regexp.MustCompile(`^[a-z0-9:]+$`) -func LooksLikeIpv4(ip string) bool { - return ipv4_pattern.MatchString(ip) +// LooksLikeIPv4 returns true if the IP looks like it's IPv4. This does not +// validate whether the string is a valid IP address. +func LooksLikeIPv4(ip string) bool { + return ipv4Pattern.MatchString(ip) } -func LooksLikeIpv6(ip string) bool { +// LooksLikeIPv6 returns true if the IP looks like it's IPv6. This does not +// validate whether the string is a valid IP address. +func LooksLikeIPv6(ip string) bool { if !strings.Contains(ip, ":") { return false } - return ipv6_pattern.MatchString(ip) + return ipv6Pattern.MatchString(ip) } +// Hostname represents a hosts file entry, including a Domain, IP, whether the +// Hostname is enabled (uncommented in the hosts file), and whether the IP is +// in the IPv6 format. You should always create these with NewHostname(). Note: +// when using Hostnames in the context of a Hostlist, you should not change the +// Hostname fields except through the Hostlist's aggregate methods. Doing so +// can cause unexpected behavior. Instead, use Hostlist's Add, Remove, Enable, +// and Disable methods. type Hostname struct { Domain string - Ip net.IP + IP net.IP Enabled bool - Ipv6 bool + IPv6 bool } +// NewHostname creates a new Hostname struct and automatically sets the IPv6 +// field based on the IP you pass in. func NewHostname(domain, ip string, enabled bool) (hostname *Hostname) { IP := net.ParseIP(ip) - hostname = &Hostname{domain, IP, enabled, LooksLikeIpv6(ip)} + hostname = &Hostname{domain, IP, enabled, LooksLikeIPv6(ip)} return } +// Equal compares two Hostnames. Note that only the Domain and IP fields are +// compared because Enabled is transient state, and IPv6 should be set +// automatically based on IP. func (h *Hostname) Equal(n *Hostname) bool { - return h.Ip.Equal(n.Ip) && h.Domain == n.Domain + return h.Domain == n.Domain && h.IP.Equal(n.IP) } -func (h *Hostname) EqualIp(ip net.IP) bool { - return h.Ip.Equal(ip) +// EqualIP compares an IP against this Hostname. +func (h *Hostname) EqualIP(ip net.IP) bool { + return h.IP.Equal(ip) } +// IsValid does a spot-check on the domain and IP to make sure they aren't blank func (h *Hostname) IsValid() bool { - return h.Domain != "" && h.Ip != nil + return h.Domain != "" && h.IP != nil } +// Format outputs the Hostname as you'd see it in a hosts file, with a comment +// if it is disabled. E.g. +// # 127.0.0.1 blah.example.com func (h *Hostname) Format() string { - r := fmt.Sprintf("%s %s", h.Ip.String(), h.Domain) + r := fmt.Sprintf("%s %s", h.IP.String(), h.Domain) if !h.Enabled { r = "# " + r } return r } -func (a *Hostname) Equals(b Hostname) bool { - if a.Domain == b.Domain && a.Ip.Equal(b.Ip) { - return true +// FormatEnabled displays Hostname.Enabled as (On) or (Off) +func (h *Hostname) FormatEnabled() string { + if h.Enabled { + return "(On)" } - return false + return "(Off)" +} + +// Format outputs the Hostname in a more human-readable format: +// blah.example.com -> 127.0.0.1 (Off) +func (h *Hostname) FormatHuman() string { + return fmt.Sprintf("%s -> %s %s", h.Domain, h.IP, h.FormatEnabled()) }