Fix file generation logic

This commit is contained in:
Diego Fernando Carrión
2019-07-16 01:38:52 +02:00
parent 07dfddb4de
commit 2becec28d2
5 changed files with 229 additions and 47 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.gopath/*
vendor/*
godocs/*
bin/*

27
Gopkg.lock generated Normal file
View File

@ -0,0 +1,27 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/anaskhan96/soup"
packages = ["."]
revision = "50123c340ba50505026229a3fb7e0bc5343e7e4d"
version = "v1.1.1"
[[projects]]
name = "github.com/karrick/godirwalk"
packages = ["."]
revision = "73c17a9b9528eb3ce857b782a2816c0cda581e62"
version = "v1.10.12"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["html","html/atom"]
revision = "da137c7871d730100384dbcf36e6f8fa493aef5b"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "440f537b71a10485068d4828ef2242afbb4f914c3b7efee0f233c097b8499a47"
solver-name = "gps-cdcl"
solver-version = 1

38
Gopkg.toml Normal file
View File

@ -0,0 +1,38 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/anaskhan96/soup"
version = "1.1.1"
[[constraint]]
name = "github.com/karrick/godirwalk"
version = "1.10.12"
[[constraint]]
branch = "master"
name = "golang.org/x/net"
[[constraint]]
branch = "master"
name = "golang.org/x/sys"

68
Makefile Normal file
View File

@ -0,0 +1,68 @@
PACKAGE = gitlab.com/CRThaze/static-godoc
GOPATH = $(CURDIR)/.gopath
BASE = $(GOPATH)/src/$(PACKAGE)
BIN = $(GOPATH)/bin
GOLINT = $(BIN)/golint
GODOC = $(BIN)/godoc
PKGS = $(or $(PKG), $(shell cd $(BASE) && \
env GOPATH=$(GOPATH) $(GO) list ./... | grep -v "^$(PACKAGE)/vendor/"))
GO = go
DEP = dep
.DEFAULT_GOAL := all
$(BASE):
@mkdir -p $(dir $@)
@ln -sf $(CURDIR) $@
.PHONY: all
all: | $(BASE)
cd $(BASE) && $(GO) build -o bin/$(PACKAGE) main.go
.PHONY: vendor-init
vendor-init: | $(BASE)
cd $(BASE) && $(DEP) init
.PHONY: vendor-add
vendor-add: | $(BASE)
cd $(BASE) && $(DEP) ensure -add ${ADD}
.PHONY: vendor
vendor: | $(BASE)
cd $(BASE) && $(DEP) ensure
$(BIN)/golint: | $(BASE)
cd $(BASE) && $(GO) get golang.org/x/lint/golint
.PHONY: docs
docs:
build_scripts/generate_godocs.py
tar czf docs.tar.gz -C godocs .
.PHONY: vet
vet: | $(BASE)
@cd $(BASE) && ret=0 && for pkg in $(PKGS); do \
test -z "$$($(GO) vet $$pkg | tee /dev/stderr)" || ret=1 ; \
done ; exit $$ret
.PHONY: lint
lint: vendor | $(BASE) $(GOLINT)
@cd $(BASE) && ret=0 && for pkg in $(PKGS); do \
test -z "$$($(GOLINT) $$pkg | tee /dev/stderr)" || ret=1 ; \
done ; exit $$ret
TIMEOUT = 20
TEST_TARGETS := test-default test-bench test-short test-verbose test-race
.PHONY: $(TEST_TARGETS) check test tests
test-bench: ARGS=-run=__absolutelynothing__ -bench=.
test-short: ARGS=-short
test-verbose: ARGS=-v
test-race: ARGS=-race
$(TEST_TARGETS): test
check test tests: lint | $(BASE)
@cd $(BASE) && $(GO) test -timeout $(TIMEOUT)s $(ARGS) ./...

139
main.go
View File

@ -5,7 +5,6 @@ import (
"container/list"
"flag"
"fmt"
"golang.org/x/sys/unix"
"io"
"io/ioutil"
"os"
@ -20,12 +19,15 @@ import (
walk "github.com/karrick/godirwalk"
)
// HashTable is a set implemented with a hashmap holding empty structs.
type HashTable map[string]struct{}
// Add adds an element to the HashTable.
func (h HashTable) Add(val string) {
h[val] = struct{}{}
}
// Has returns true if the HashTable contains a string, and false otherwise.
func (h HashTable) Has(val string) bool {
if _, ok := h[val]; !ok {
return false
@ -33,13 +35,15 @@ func (h HashTable) Has(val string) bool {
return true
}
// ToSlice returns the HashTable as a slice of strings.
func (h HashTable) ToSlice() []string {
result := make([]string, len(h))
var i uint
for val, _ := range h {
for val := range h {
result[i] = val
i++
}
return result
}
const (
@ -76,6 +80,7 @@ var args struct {
outputPath string
pkgRoot string
godocCmd string
verbose bool
exclude string
excludedDirs HashTable
}
@ -114,10 +119,16 @@ func initAndValidateArgs() {
"godoc",
"The executable to use for generating go docs.",
)
flag.BoolVar(
&args.verbose,
"v",
false,
"Whether to print verbose output.",
)
flag.StringVar(
&args.exclude,
"exclude",
"vendor",
"vendor,testdata",
"Comma-delimited list of directory names which should be excluded.",
)
flag.Parse()
@ -130,6 +141,9 @@ func initAndValidateArgs() {
for _, dir := range strings.Split(args.exclude, ",") {
args.excludedDirs.Add(dir)
}
if !strings.HasPrefix(args.outputPath, "/") {
args.outputPath = args.pkgRoot + "/" + args.outputPath
}
if args.pkgName == "" && args.goPath != "" {
abs, err := filepath.Abs(args.pkgRoot)
@ -147,7 +161,9 @@ func initAndValidateArgs() {
validationResults := map[string]bool{
"-pkg-name must be provided if not under GOPATH/src directory": args.pkgName != "",
"-gopath must be provided if $GOPATH environment variable not set": args.goPath != "",
"-root must be under the GOPATH": strings.HasPrefix(args.goPath),
"-root must be under the GOPATH": strings.HasPrefix(
args.pkgRoot, args.goPath,
),
"-godoc-cmd must exist": func() bool {
if _, err := exec.LookPath(args.godocCmd); err != nil {
return false
@ -165,22 +181,34 @@ func initAndValidateArgs() {
}
}
func verboseLogf(format string, v ...interface{}) {
if args.verbose {
fmt.Printf(format+"\n", v...)
}
}
func verboseLog(v ...interface{}) {
if args.verbose {
fmt.Println(v...)
}
}
type pkgInfo struct {
isGoPkg bool
subPkgs HashTable
doc []byte
}
var pkgs = map[string]PkgInfo{}
var pkgs = map[string]*pkgInfo{}
func extractNode(node soup.Root) {
func extractNode(node *soup.Root) {
if node.Pointer == nil {
return
}
node.Pointer.Parent.RemoveChild(node.Pointer)
}
func removeAttr(node soup.Root, key string) {
func removeAttr(node *soup.Root, key string) {
for i, attr := range node.Pointer.Attr {
if attr.Key == key {
node.Pointer.Attr = append(node.Pointer.Attr[:i], node.Pointer.Attr[i+1:]...)
@ -188,48 +216,51 @@ func removeAttr(node soup.Root, key string) {
}
}
func updateAttr(node soup.Root, key string, val string) {
for _, attr := range node.Pointer.Attr {
func updateAttr(node *soup.Root, key string, val string) {
for i, attr := range node.Pointer.Attr {
if attr.Key == key {
attr.Val = val
node.Pointer.Attr[i].Val = val
}
}
}
func modifyHTML(doc string, pkgPath string, subPkgs HashTable) (string, error) {
tagSoup = soup.HTMLParse(doc)
func modifyHTML(doc string, pkgPath string, subPkgs HashTable) ([]byte, error) {
tagSoup := soup.HTMLParse(doc)
// Prepend golang.org to all the links in the <head> tag.
for link := range tagSoup.Find("head").FindAll("link") {
for _, link := range tagSoup.Find("head").FindAll("link") {
attrs := link.Attrs()
updateAttr(link, "href", "https://golang.org"+attrs["href"])
updateAttr(&link, "href", "https://golang.org"+attrs["href"])
}
// Remove the topbar and footer.
extractNode(tagSoup.FindStrict("div", "id", "topbar"))
extractNode(tagSoup.FindStrict("div", "id", "footer"))
topbar := tagSoup.FindStrict("div", "id", "topbar")
extractNode(&topbar)
footer := tagSoup.FindStrict("div", "id", "footer")
extractNode(&footer)
// Delete all pkg-name links which are not valid subpackages.
for td := range tagSoup.FindAllStrict("td", "id", "pkgName") {
for _, td := range tagSoup.FindAllStrict("td", "id", "pkgName") {
if subPkgs.Has(td.Find("a").Text()) {
extractNode(td)
extractNode(&td)
}
}
// Add index.html to all the pkg-dir links.
pkgDir := tagSoup.FindStrict("div", "id", "pkg-dir")
pkgDir := tagSoup.FindStrict("div", "class", "pkg-dir")
if pkgDir.Pointer != nil {
extractNode(pkgDir.FindStrict("a", "href", ".."))
for a := range pkgDir.FindAll("a") {
twoDots := pkgDir.FindStrict("a", "href", "..")
extractNode(&twoDots)
for _, a := range pkgDir.FindAll("a") {
attrs := a.Attrs()
updateAttr(a, "href", attrs["href"]+"index.html")
updateAttr(&a, "href", attrs["href"]+"index.html")
}
}
// Fix the main body links.
page := tagSoup.Find("div", "id", "page")
if page.Pointer != nil {
for a := range page.FindAll("a") {
for _, a := range page.FindAll("a") {
attrs := a.Attrs()
// Ensure all subpackage links are relative.
@ -240,15 +271,15 @@ func modifyHTML(doc string, pkgPath string, subPkgs HashTable) (string, error) {
),
)
if err != nil {
return "", err
return []byte{}, err
}
updateAttr(a, "href", re.ReplaceAllString(attrs["href"], "$1/index.html$2"))
updateAttr(&a, "href", re.ReplaceAllString(attrs["href"], "$1/index.html$2"))
// Make all remaining /pkg or /doc links point to godoc.og
if strings.HasPrefix(attrs["href"], "/pkg/") || strings.HasPrefix(attrs["href"], "/doc/") {
updateAttr(a, "href", "https://godoc.org"+attrs["href"])
updateAttr(&a, "href", "https://godoc.org"+attrs["href"])
} else if strings.HasPrefix(attrs["href"], "/src/") { // Disable all /src links.
removeAttr(a, "href")
removeAttr(&a, "href")
}
}
}
@ -256,12 +287,22 @@ func modifyHTML(doc string, pkgPath string, subPkgs HashTable) (string, error) {
// Render full modified document and return.
var buf bytes.Buffer
w := io.Writer(&buf)
html.Render(w, soup.Pointer)
return buf.String(), nil
html.Render(w, tagSoup.Pointer)
return buf.Bytes(), nil
}
func runGoDoc(url string) ([]byte, error) {
verboseLog(url)
out, err := exec.Command(args.godocCmd, "-url", url).Output()
if err != nil {
return []byte{}, err
}
return out, nil
}
func writePkgDoc(pkgPath string, subPkgs HashTable) error {
doc, err := runGoDoc(strings.TrimPrefix(path, args.goPath))
pkgName := strings.TrimPrefix(pkgPath, args.goPath+"/src/")
doc, err := runGoDoc("/pkg/" + pkgName)
if err != nil {
return err
}
@ -269,8 +310,15 @@ func writePkgDoc(pkgPath string, subPkgs HashTable) error {
if err != nil {
return err
}
outputFile := fmt.Sprint(
args.outputPath+"/",
strings.TrimPrefix(pkgPath, args.pkgRoot),
"/index.html",
)
verboseLogf("Writing %s documentation to %s", pkgName, outputFile)
os.MkdirAll(filepath.Dir(outputFile), os.ModePerm)
if err := ioutil.WriteFile(
fmt.Sprint(pkgPath, "/index.html"),
outputFile,
correctedHTML,
0644,
); err != nil {
@ -279,31 +327,25 @@ func writePkgDoc(pkgPath string, subPkgs HashTable) error {
return nil
}
func runGoDoc(url string) (string, error) {
out, err := exec.Command(args.godocCmd, "-url", url).Output()
if err != nil {
return "", err
}
return out, nil
}
func genDocs() error {
// Walk the Package Root.
verboseLogf("Walking %s...", args.pkgRoot)
return walk.Walk(
args.pkgRoot,
&walk.Options{
// Define callback to run for every entity found.
Callback: func(path string, de *walk.Dirent) error {
verboseLogf("Examining %s", path)
if de.IsDir() {
// Skip excluded and hidden directories.
if args.excludedDirs.Has(de.Name()) || de.Name()[0] == "." {
if args.excludedDirs.Has(de.Name()) || strings.HasPrefix(de.Name(), ".") {
return filepath.SkipDir
}
// Add newly found directory to the map of potential packages.
pkgs[path] = pkgInfo{}
pkgs[path] = &pkgInfo{subPkgs: HashTable{}}
return nil
}
dirName = filepath.Dir(path)
dirName := filepath.Dir(path)
// If we have already determined this directory is a Go package we don't need to do more.
if pkgs[dirName].isGoPkg {
return nil
@ -311,9 +353,11 @@ func genDocs() error {
// Check if this file ends in .go.
// If it does then the directory it is in is a Go package and it should be added to its
// parents list of sub-packages.
if len(de.Name()) > 3 && de.Name()[:len(de.Name())-3] == ".go" {
pkgs[filepath.Dir(path)].isGoPkg = true
parentName = filepath.Dir(dirName)
if len(de.Name()) > 3 && de.Name()[len(de.Name())-3:] == ".go" {
p := pkgs[dirName]
p.isGoPkg = true
verboseLogf("%s is a Go package.", dirName)
parentName := filepath.Dir(dirName)
if parent, ok := pkgs[parentName]; ok {
parent.subPkgs.Add(filepath.Base(dirName))
}
@ -323,13 +367,14 @@ func genDocs() error {
// Define callback to run after processing all children for a directory.
PostChildrenCallback: func(path string, de *walk.Dirent) error {
if !(pkgs[path].isGoPkg) {
return
return nil
}
verboseLogf("Generating Documentation for %s", path)
return writePkgDoc(path, pkgs[path].subPkgs)
},
// Define callback for handling errors.
ErrorCallback: func(path string, err error) walk.ErrorAction {
fmt.Printf("ERROR: Failed while processing '%s': %v", err)
fmt.Printf("ERROR: Failed while processing '%s': %v", path, err)
return walk.Halt
},
},