This is my first go at a Go program that's not a variation of "Hello {$name}". It's about twice as long as my previous Python implementation, and perhaps an order of magnitude messier.
The goal is to read in /etc/hosts and combine the various entries I've made over time:
127.0.0.1 host1.test
127.0.0.1 host2.test
111.111.111.111 staging.dev otherproject.dev
127.0.0.1 localhost host.test
into:
127.0.0.1 host.test host1.test host2.test localhost
111.111.111.111 otherproject.dev staging.dev
Any thoughts on how to make this cleaner, better, and/or more idiomatic?
package main
import (
"os"
"io/ioutil"
"strings"
"sort"
)
// Because this doesn't exist?
type StringSet struct {
set map[string]bool
}
func NewStringSet() StringSet {
return StringSet{ make(map[string]bool) }
}
// Returns true if added, false if already exists
func (s StringSet) Add(str string) bool {
_, found := s.set[str]
s.set[str] = true
return !found
}
// Returns the hostnames in alphabetical order
func (s StringSet) GetHostnames() []string {
keys := make([]string, 0, len(s.set))
for key := range s.set {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
// Because this doesn't exist?
func pop(s []string) (string, []string) {
var x string
x, s = s[0], s[1:len(s)]
return x, s
}
// Returns the lines of a file
func GetLines(filename string) []string {
raw, err := ioutil.ReadFile("/etc/hosts")
if err != nil {
panic(err)
}
return strings.Split(string(raw[:]), "\n")
}
func WriteHostsFile(outfile string, hosts map[string]StringSet) bool {
// Range outputs a random order -.-
fd, err := os.Create(outfile)
if err != nil {
panic(err)
}
defer fd.Close()
fd.WriteString("# Managed by hosts.go\n")
for k := range hosts {
names := hosts[k].GetHostnames()
line := k + " " + strings.Join(names, " ")
fd.WriteString(line + "\n")
}
return true
}
func ProcessHosts(data []string) map[string]StringSet {
hosts := make(map[string]StringSet)
for _, line := range data {
var ip string
parts := strings.Split(line, " ")
if len(parts) > 1 {
ip, parts = pop(parts)
if ip[0] == '#' {
continue
}
// Check if nil map
if len(hosts[ip].set) == 0 {
hosts[ip] = NewStringSet()
}
for _, word := range parts {
hosts[ip].Add(word)
}
}
}
return hosts
}
func main() {
data := GetLines("/etc/hosts")
hosts := ProcessHosts(data)
WriteHostsFile("hosts", hosts)
}
pop
andStringSet
). To truly learn Go, I would highly advise rethinking the problem in terms of what's available in Go (maps and slices) and then doing it from scratch, rather than creating new generic structures. Most notably, I'd use a map[string][]string to store IP -> slice of hostnames and not care about duplicates, then remove them after you sort the slice. \$\endgroup\$