Fix file generation logic
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.gopath/*
|
||||
vendor/*
|
||||
godocs/*
|
||||
bin/*
|
27
Gopkg.lock
generated
Normal file
27
Gopkg.lock
generated
Normal 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
38
Gopkg.toml
Normal 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
68
Makefile
Normal 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
139
main.go
@ -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
|
||||
},
|
||||
},
|
||||
|
Reference in New Issue
Block a user