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 finalROFF
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 | |
} | |
} |
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.
-
PHP doesn’t have any support for concurrency. ↩