Add --exclude flag and fix compilation on Windows

This commit is contained in:
Trevor Slocum 2020-02-07 07:38:30 -08:00
parent 658b6bd322
commit 6387f68699
6 changed files with 150 additions and 79 deletions

View file

@ -1,3 +1,7 @@
0.1.3:
- Add --exclude flag
- Fix compilation on Windows
0.1.2:
- Copy all source file types

View file

@ -12,11 +12,15 @@ Generate static Go documentation
Install `godoc-static`:
`go get gitlab.com/tslocum/godoc-static`
```bash
go get gitlab.com/tslocum/godoc-static
```
Also install `godoc`:
`go get golang.org/x/tools/cmd/godoc`
```bash
go get golang.org/x/tools/cmd/godoc
```
## Documentation
@ -26,11 +30,21 @@ Execute `godoc-static` with the `-help` flag for more information.
Generate documentation for `archive`, `fmt` and `net/http` targeting `https://docs.rocketnine.space`:
`godoc-static -base-path=/ -site-name="Rocket Nine Labs Documentation" -site-description="Welcome!" -out=/home/user/sites/docs archive fmt net/http`
```bash
godoc-static -base-path=/ -site-name="Rocket Nine Labs Documentation" \
-site-description="Welcome!" \
-out=/home/user/sites/docs \
archive fmt net/http
```
Targeting `https://rocketnine.space/docs/`:
`godoc-static -base-path=/docs/ -site-name="Rocket Nine Labs Documentation" -site-description-file=/home/user/sitefiles/description.md -out=/home/user/sites/docs archive fmt net/http`
```bash
godoc-static -base-path=/docs/ -site-name="Rocket Nine Labs Documentation" \
-site-description-file=/home/user/sitefiles/description.md \
-out=/home/user/sites/docs \
archive fmt net/http
```
## Support

7
cmd.go Normal file
View file

@ -0,0 +1,7 @@
//+build !linux
package main
import "os/exec"
func setDeathSignal(cmd *exec.Cmd) {}

14
cmd_linux.go Normal file
View file

@ -0,0 +1,14 @@
//+build linux
package main
import (
"os/exec"
"syscall"
)
func setDeathSignal(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}
}

143
main.go
View file

@ -2,6 +2,7 @@ package main
import (
"bytes"
"errors"
"flag"
"fmt"
"io/ioutil"
@ -9,11 +10,11 @@ import (
"net/http"
"os"
"os/exec"
"os/signal"
"path"
"path/filepath"
"sort"
"strings"
"syscall"
"time"
"github.com/PuerkitoBio/goquery"
@ -23,11 +24,6 @@ import (
"golang.org/x/net/html"
)
const additionalCSS = `
details { margin-top: 20px; }
summary { margin-left: 20px; cursor: pointer; }
`
var (
listenAddress string
basePath string
@ -36,7 +32,10 @@ var (
siteDescriptionFile string
linkIndex bool
outDir string
excludePackages string
verbose bool
godoc *exec.Cmd
)
func main() {
@ -50,20 +49,31 @@ func main() {
flag.StringVar(&siteDescriptionFile, "site-description-file", "", "path to markdown file containing site description")
flag.BoolVar(&linkIndex, "link-index", false, "set link targets to index.html instead of folder")
flag.StringVar(&outDir, "out", "", "site directory")
flag.StringVar(&excludePackages, "exclude", "", "list of packages to exclude from index")
flag.BoolVar(&verbose, "verbose", false, "enable verbose logging")
flag.Parse()
err := run()
if godoc != nil {
godoc.Process.Kill()
}
if err != nil {
log.Fatal(err)
}
}
func run() error {
var buf bytes.Buffer
timeStarted := time.Now()
if outDir == "" {
log.Fatal("--out must be set")
return errors.New("--out must be set")
}
if siteDescriptionFile != "" {
siteDescriptionBytes, err := ioutil.ReadFile(siteDescriptionFile)
if err != nil {
log.Fatalf("failed to read site description file %s: %s", siteDescriptionFile, err)
return fmt.Errorf("failed to read site description file %s: %s", siteDescriptionFile, err)
}
siteDescription = string(siteDescriptionBytes)
}
@ -81,7 +91,7 @@ func main() {
buf.Reset()
err := markdown.Convert([]byte(siteDescription), &buf)
if err != nil {
log.Fatalf("failed to render site description markdown: %s", err)
return fmt.Errorf("failed to render site description markdown: %s", err)
}
siteDescription = buf.String()
}
@ -90,25 +100,26 @@ func main() {
log.Println("Starting godoc...")
}
cmd := exec.Command("godoc", fmt.Sprintf("-http=%s", listenAddress))
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
godoc = exec.Command("godoc", fmt.Sprintf("-http=%s", listenAddress))
godoc.Stdin = os.Stdin
godoc.Stdout = os.Stdout
godoc.Stderr = os.Stderr
setDeathSignal(godoc)
err := cmd.Start()
err := godoc.Start()
if err != nil {
log.Fatalf("failed to execute godoc: %s", err)
return fmt.Errorf("failed to execute godoc: %s", err)
}
// Allow godoc to initialize
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
godoc.Process.Kill()
os.Exit(1)
}()
time.Sleep(3 * time.Second)
done := make(chan struct{})
timeout := time.After(15 * time.Second)
godocStarted := time.Now()
pkgs := flag.Args()
@ -123,16 +134,14 @@ func main() {
newPkgs = append(newPkgs, pkg)
listCmd := exec.Command("go", "list", "-find", "-f", `{{ .Dir }}`, pkg)
listCmd.Dir = os.TempDir()
listCmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}
listCmd.Stdout = &buf
cmd := exec.Command("go", "list", "-find", "-f", `{{ .Dir }}`, pkg)
cmd.Dir = os.TempDir()
cmd.Stdout = &buf
setDeathSignal(cmd)
err = listCmd.Run()
err = cmd.Run()
if err != nil {
log.Fatalf("failed to list source directory of package %s: %s", pkg, err)
return fmt.Errorf("failed to list source directory of package %s: %s", pkg, err)
}
pkgPath := strings.TrimSpace(buf.String())
@ -152,7 +161,7 @@ func main() {
return nil
})
if err != nil {
log.Fatalf("failed to walk source directory of package %s: %s", pkg, err)
return fmt.Errorf("failed to walk source directory of package %s: %s", pkg, err)
}
}
buf.Reset()
@ -160,7 +169,7 @@ func main() {
pkgs = uniqueStrings(newPkgs)
if len(pkgs) == 0 {
log.Fatal("failed to generate docs: provide the name of at least one package to generate documentation for")
return errors.New("failed to generate docs: provide the name of at least one package to generate documentation for")
}
filterPkgs := pkgs
@ -177,6 +186,15 @@ func main() {
return strings.ToLower(pkgs[i]) < strings.ToLower(pkgs[j])
})
// Allow godoc to initialize
if time.Since(godocStarted) < 3*time.Second {
time.Sleep((3 * time.Second) - time.Since(godocStarted))
}
done := make(chan error)
timeout := time.After(15 * time.Second)
go func() {
var (
res *http.Response
@ -198,7 +216,8 @@ func main() {
// Load the HTML document
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatalf("failed to get page of %s: %s", pkg, err)
done <- fmt.Errorf("failed to get page of %s: %s", pkg, err)
return
}
doc.Find("title").First().SetHtml(fmt.Sprintf("%s - %s", path.Base(pkg), siteName))
@ -209,33 +228,39 @@ func main() {
err = os.MkdirAll(localPkgPath, 0755)
if err != nil {
log.Fatalf("failed to make directory %s: %s", localPkgPath, err)
done <- fmt.Errorf("failed to make directory %s: %s", localPkgPath, err)
return
}
buf.Reset()
err = html.Render(&buf, doc.Nodes[0])
if err != nil {
done <- fmt.Errorf("failed to render HTML: %s", err)
return
}
err = ioutil.WriteFile(path.Join(localPkgPath, "index.html"), buf.Bytes(), 0755)
if err != nil {
log.Fatalf("failed to write docs for %s: %s", pkg, err)
done <- fmt.Errorf("failed to write docs for %s: %s", pkg, err)
return
}
}
done <- struct{}{}
done <- nil
}()
select {
case <-timeout:
log.Fatal("godoc failed to start in time")
case <-done:
return errors.New("godoc failed to start in time")
case err = <-done:
if err != nil {
return fmt.Errorf("failed to copy docs: %s", err)
}
}
// Write source files
err = os.MkdirAll(path.Join(outDir, "src"), 0755)
if err != nil {
log.Fatalf("failed to make directory lib: %s", err)
return fmt.Errorf("failed to make directory lib: %s", err)
}
for _, pkg := range filterPkgs {
@ -247,7 +272,7 @@ func main() {
// TODO Handle temp directory not existing
buf.Reset()
listCmd := exec.Command("go", "list", "-find", "-f",
cmd := exec.Command("go", "list", "-find", "-f",
`{{ join .GoFiles "\n" }}`+"\n"+
`{{ join .CgoFiles "\n" }}`+"\n"+
`{{ join .CFiles "\n" }}`+"\n"+
@ -261,15 +286,13 @@ func main() {
`{{ join .TestGoFiles "\n" }}`+"\n"+
`{{ join .XTestGoFiles "\n" }}`,
pkg)
listCmd.Dir = tmpDir
listCmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}
listCmd.Stdout = &buf
cmd.Dir = tmpDir
cmd.Stdout = &buf
setDeathSignal(cmd)
err = listCmd.Run()
err = cmd.Run()
if err != nil {
//log.Fatalf("failed to list source files of package %s: %s", pkg, err)
//return fmt.Errorf("failed to list source files of package %s: %s", pkg, err)
continue // This is expected for packages without source files
}
@ -283,13 +306,13 @@ func main() {
// Rely on timeout to break loop
res, err := http.Get(fmt.Sprintf("http://%s/src/%s/%s", listenAddress, pkg, sourceFile))
if err != nil {
log.Fatalf("failed to get source file page %s for package %s: %s", sourceFile, pkg, err)
return fmt.Errorf("failed to get source file page %s for package %s: %s", sourceFile, pkg, err)
}
// Load the HTML document
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatalf("failed to load document from page for package %s: %s", pkg, err)
return fmt.Errorf("failed to load document from page for package %s: %s", pkg, err)
}
doc.Find("title").First().SetHtml(fmt.Sprintf("%s - %s", path.Base(pkg), siteName))
@ -307,13 +330,13 @@ func main() {
err = os.MkdirAll(pkgSrcPath, 0755)
if err != nil {
log.Fatalf("failed to make directory %s: %s", pkgSrcPath, err)
return fmt.Errorf("failed to make directory %s: %s", pkgSrcPath, err)
}
buf.Reset()
err = html.Render(&buf, doc.Nodes[0])
if err != nil {
return
return fmt.Errorf("failed to render HTML: %s", err)
}
outFileName := sourceFile
@ -322,7 +345,7 @@ func main() {
}
err = ioutil.WriteFile(path.Join(pkgSrcPath, outFileName), buf.Bytes(), 0755)
if err != nil {
log.Fatalf("failed to write docs for %s: %s", pkg, err)
return fmt.Errorf("failed to write docs for %s: %s", pkg, err)
}
}
}
@ -335,25 +358,25 @@ func main() {
err = os.MkdirAll(path.Join(outDir, "lib"), 0755)
if err != nil {
log.Fatalf("failed to make directory lib: %s", err)
return fmt.Errorf("failed to make directory lib: %s", err)
}
res, err := http.Get(fmt.Sprintf("http://%s/lib/godoc/style.css", listenAddress))
if err != nil {
log.Fatalf("failed to get syle.css: %s", err)
return fmt.Errorf("failed to get syle.css: %s", err)
}
content, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatalf("failed to get style.css: %s", err)
return fmt.Errorf("failed to get style.css: %s", err)
}
content = append(content, []byte("\n"+additionalCSS+"\n")...)
err = ioutil.WriteFile(path.Join(outDir, "lib", "style.css"), content, 0755)
if err != nil {
log.Fatalf("failed to write index: %s", err)
return fmt.Errorf("failed to write index: %s", err)
}
// Write index
@ -362,11 +385,15 @@ func main() {
log.Println("Writing index...")
}
writeIndex(&buf, outDir, basePath, siteName, pkgs, filterPkgs)
err = writeIndex(&buf, outDir, basePath, siteName, pkgs, filterPkgs)
if err != nil {
return fmt.Errorf("failed to write index: %s", err)
}
if verbose {
log.Printf("Generated documentation in %s.", time.Since(timeStarted).Round(time.Second))
}
return nil
}
func uniqueStrings(strSlice []string) []string {

39
page.go
View file

@ -4,20 +4,22 @@ import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"strconv"
"strings"
"syscall"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
"github.com/PuerkitoBio/goquery"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
const additionalCSS = `
details { margin-top: 20px; }
summary { margin-left: 20px; cursor: pointer; }
`
func topBar(basePath string, siteName string) string {
var index string
if linkIndex {
@ -128,7 +130,7 @@ func updatePage(doc *goquery.Document, basePath string, siteName string) {
doc.Find("#footer").Last().Remove()
}
func writeIndex(buf *bytes.Buffer, outDir string, basePath string, siteName string, pkgs []string, filterPkgs []string) {
func writeIndex(buf *bytes.Buffer, outDir string, basePath string, siteName string, pkgs []string, filterPkgs []string) error {
var index string
if linkIndex {
index = "/index.html"
@ -174,16 +176,22 @@ func writeIndex(buf *bytes.Buffer, outDir string, basePath string, siteName stri
var padding int
var lastPkg string
var pkgBuf bytes.Buffer
excludePackagesSplit := strings.Split(excludePackages, " ")
PACKAGEINDEX:
for _, pkg := range pkgs {
pkgBuf.Reset()
listCmd := exec.Command("go", "list", "-find", "-f", `{{ .Doc }}`, pkg)
listCmd.Dir = os.TempDir()
listCmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
for _, excludePackage := range excludePackagesSplit {
if pkg == excludePackage || strings.HasPrefix(pkg, excludePackage+"/") {
continue PACKAGEINDEX
}
}
listCmd.Stdout = &pkgBuf
_ = listCmd.Run() // Ignore error
pkgBuf.Reset()
cmd := exec.Command("go", "list", "-find", "-f", `{{ .Doc }}`, pkg)
cmd.Dir = os.TempDir()
cmd.Stdout = &pkgBuf
setDeathSignal(cmd)
cmd.Run() // Ignore error
pkgLabel := pkg
if lastPkg != "" {
@ -232,8 +240,5 @@ func writeIndex(buf *bytes.Buffer, outDir string, basePath string, siteName stri
</html>
`)
err := ioutil.WriteFile(path.Join(outDir, "index.html"), buf.Bytes(), 0755)
if err != nil {
log.Fatalf("failed to write index: %s", err)
}
return ioutil.WriteFile(path.Join(outDir, "index.html"), buf.Bytes(), 0755)
}