Friday, August 13, 2010

A quick tour of Go's dict package

The 2010-08-11 release of the Go programming language includes the dict package to talk to dictionary servers as defined by RFC 2229. Let's check it out.

First, here's a mimimal program that uses the Dial() and Define() functions to display a definition.

package main

import "net/dict"

func main() {
c, _ := dict.Dial("tcp", "dict.org:2628")
defn, _ := c.Define("wn", "go")
for _, result := range defn {
println(string(result.Text))
}
}


This program does no error checking, and has three hardcoded bits: the server (dict.org:2628), the database ("wn") and word to lookup ("go").

For the next version, let's add some flexibility -- lookup items specified on the command line, and let's add some error checking and cleanup:

package main

import (
"net/dict"
"os"
)

func main() {
c, neterr := dict.Dial("tcp", "dict.org:2628")
if neterr == nil {
defer c.Close()
for _, word := range os.Args {
defn, deferr := c.Define("wn", word)
if deferr == nil {
for _, result := range defn {
println(string(result.Text))
}
}
}
}
}

Ok, great, what if you want to switch databases? You can ask the server. If you run the command with no arguments, it will list the available databases using the Dicts() function:

package main

import (
"net/dict"
"os"
)

func main() {
c, neterr := dict.Dial("tcp", "dict.org:2628")
if neterr == nil {
defer c.Close()
if len(os.Args) == 0 {
dicts, dicterr := c.Dicts()
if dicterr == nil {
for _, dl := range dicts {
println(dl.Name, dl.Desc)
}
}
} else {
for _, word := range os.Args {
defn, deferr := c.Define("wn", word)
if deferr == nil {
for _, result := range defn {
println(string(result.Text))
}
}
}
}
}
}

One more update: let's add flags to specify the dict server and database. We'll also update the error processing to show the errors instead of failing silently:

package main

import (
"net/dict"
"flag"
"fmt"
)
var (
db = flag.String("d", "wn", "Dictionary database")
dserver = flag.String("s", "dict.org:2628", "Dictionary Server")
)

func main() {
flag.Parse()
c, neterr := dict.Dial("tcp", *dserver)
if neterr == nil {
defer c.Close()
if len(flag.Args()) == 0 {
dicts, dicterr := c.Dicts()
if dicterr == nil {
for _, dl := range dicts {
fmt.Println(dl.Name, dl.Desc)
}
} else {
fmt.Println(dicterr)
}
} else {
for _, word := range flag.Args() {
defn, dferr := c.Define(*db, word)
if dferr == nil {
for _, result := range defn {
fmt.Println(string(result.Text))
}
} else {
fmt.Println(dferr)
}
}
}
} else {
fmt.Println(neterr)
}
}

There you go--a robust, flexible dictionary client in a little over 40 lines of code.

2 comments:

Anonymous said...

Your code will be cleaner (and more idiomatic) if you check for errors and then do something instead of putting the whole program inside an if block, ie:

if err != nil {
handleError(err)
}
stuff()

instead of:

if err == nil {
stuff()
}

I know this is a minor issue, but all those closing braces at the end of the program are really ugly, and it uses to be a good idea to follow conventions.

That said, nice example :)

Anonymous said...

For little programs, I find myself using

func dieOnError(e *os.Error) {
if e != nil {
panic(e.String())
}
}

(Of course, one needs to be careful with os.EOF)