gopher

I’ve had this idea for a fun weekend project for a while: generate the WP-CLI manual concurrently, instead of one page at a time.

All I needed was the right programming language 1. Since I’ve heard good things about Go, I decided to give it a try. I took the Go Tour and then just started hacking.

A WP-CLI manual page is generated by:

  • collecting data from inline PHP docComents
  • reading a markdown file with additional documentation
  • running everything through ronn to generate the final ROFF output

At first, I tried to do all these steps in Go, but soon discovered that it would duplicate too much code. Eventually, I settled on just letting WP-CLI generate each page, using the existing logic.

So, I ended up with less than 100 lines of Go code:

package main
import (
"bytes"
"encoding/json"
"io"
"log"
"os"
"os/exec"
"strings"
)
var WP_CLI_PATH, WP_PATH string
type Command struct {
Name, Synopsis, Description string
Subcommands []Command
}
func getCommandsAsJSON() bytes.Buffer {
cmd := exec.Command(WP_CLI_PATH+"/bin/wp", "--cmd-dump")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
return out
}
func decodeJSON(out bytes.Buffer) Command {
dec := json.NewDecoder(bytes.NewReader(out.Bytes()))
var c Command
for {
if err := dec.Decode(&c); err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
}
return c
}
func generateMan(part string, done chan bool) {
defer func() { done <- true }()
cmd := exec.Command(WP_CLI_PATH+"/bin/wp", part, "--man")
cmd.Dir = WP_PATH
out, _ := cmd.CombinedOutput()
log.Println(part)
log.Println(strings.Trim(string(out), "\n"))
}
func main() {
if len(os.Args) < 3 {
log.Fatal("usage: go run man.go /path/to/wp-cli /path/to/wp-dir")
}
WP_CLI_PATH = os.Args[1]
WP_PATH = os.Args[2]
wp := decodeJSON(getCommandsAsJSON())
done := make(chan bool)
for _, cmd := range wp.Subcommands {
go generateMan(cmd.Name, done)
}
for i := len(wp.Subcommands); i > 0; i-- {
<-done
}
}
view raw man.go hosted with ❤ by GitHub

I’ve tried on several occasions to use threads in various programming languages, but never felt I really mastered them. By contrast, I was able to grok goroutines and channels in a weekend; they just made sense.

In general, the Go documentation is the best I’ve seen so far. For instance, when you look up a function from the standard library, besides a helpful example, there’s also a link to its source code.

I also like go fmt, which takes care of formatting your code; no more worrying about spaces vs. tabs etc.

Even if it yielded a modest 23% speedup (from 17 seconds down to 13 seconds to compile all the pages), it was a worthwhile experiment.

  1. PHP doesn’t have any support for concurrency.