408 lines
9.3 KiB
Go
408 lines
9.3 KiB
Go
|
// Copyright 2011-2015 visualfc <visualfc@gmail.com>. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package docview
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"go/build"
|
||
|
"io"
|
||
|
"log"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"runtime"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"text/template"
|
||
|
"time"
|
||
|
|
||
|
"github.com/visualfc/gotools/command"
|
||
|
)
|
||
|
|
||
|
var Command = &command.Command{
|
||
|
Run: runDocView,
|
||
|
UsageLine: "docview [-mode] [-list|-find]",
|
||
|
Short: "golang docview util",
|
||
|
Long: `golang docview util`,
|
||
|
}
|
||
|
|
||
|
var goroot = runtime.GOROOT()
|
||
|
|
||
|
var docViewFind string
|
||
|
var docViewList string
|
||
|
var docViewMode string
|
||
|
|
||
|
func init() {
|
||
|
Command.Flag.StringVar(&docViewFind, "find", "", "find package list, :pkg flag is best match")
|
||
|
Command.Flag.StringVar(&docViewList, "list", "", "Print go packages list [pkg|cmd]")
|
||
|
Command.Flag.StringVar(&docViewMode, "mode", "text", "Print mode [text|html|lite]")
|
||
|
}
|
||
|
|
||
|
func runDocView(cmd *command.Command, args []string) error {
|
||
|
if docViewFind == "" && docViewList == "" {
|
||
|
cmd.Usage()
|
||
|
return os.ErrInvalid
|
||
|
}
|
||
|
|
||
|
var template string
|
||
|
var info *Info
|
||
|
if len(docViewList) > 0 {
|
||
|
pkgPath := filepath.Join(goroot, "src", docViewList)
|
||
|
if docViewList == "pkg" {
|
||
|
_, err := os.Stat(pkgPath)
|
||
|
if err != nil {
|
||
|
pkgPath = filepath.Join(goroot, "src")
|
||
|
}
|
||
|
}
|
||
|
info = NewListInfo(pkgPath)
|
||
|
if info != nil {
|
||
|
if docViewList == "pkg" {
|
||
|
var filterList []DirEntry
|
||
|
for _, v := range info.Dirs.List {
|
||
|
if v.Path == "cmd" {
|
||
|
continue
|
||
|
}
|
||
|
if strings.HasPrefix(v.Path, "cmd/") {
|
||
|
continue
|
||
|
}
|
||
|
if strings.Contains(v.Path, "/testdata") {
|
||
|
continue
|
||
|
}
|
||
|
filterList = append(filterList, v)
|
||
|
}
|
||
|
info.Dirs.List = filterList
|
||
|
} else if docViewList == "cmd" {
|
||
|
var filterList []DirEntry
|
||
|
for _, v := range info.Dirs.List {
|
||
|
if strings.Contains(v.Path, "/") {
|
||
|
continue
|
||
|
}
|
||
|
if strings.Contains(v.Path, "internal") {
|
||
|
continue
|
||
|
}
|
||
|
filterList = append(filterList, v)
|
||
|
}
|
||
|
info.Dirs.List = filterList
|
||
|
}
|
||
|
}
|
||
|
switch docViewMode {
|
||
|
case "html":
|
||
|
template = listHTML
|
||
|
case "lite":
|
||
|
template = listLite
|
||
|
case "text":
|
||
|
template = listText
|
||
|
default:
|
||
|
template = listText
|
||
|
}
|
||
|
} else if len(docViewFind) > 0 {
|
||
|
dir := NewSourceDir(goroot)
|
||
|
info = dir.FindInfo(docViewFind)
|
||
|
switch docViewMode {
|
||
|
case "html":
|
||
|
template = findHTML
|
||
|
case "lite":
|
||
|
template = findLite
|
||
|
case "text":
|
||
|
template = findText
|
||
|
default:
|
||
|
template = findText
|
||
|
}
|
||
|
}
|
||
|
if info == nil {
|
||
|
fmt.Fprintf(os.Stderr, "<error>\n")
|
||
|
command.SetExitStatus(3)
|
||
|
command.Exit()
|
||
|
}
|
||
|
contents := info.GetPkgList(docViewMode, template)
|
||
|
fmt.Fprintf(os.Stdout, "%s", contents)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
fs FileSystem = OS // the underlying file system
|
||
|
)
|
||
|
|
||
|
// Fake package file and name for commands. Contains the command documentation.
|
||
|
const fakePkgFile = "doc.go"
|
||
|
const fakePkgName = "documentation"
|
||
|
|
||
|
func textFmt(w io.Writer, format string, x ...interface{}) {
|
||
|
var buf bytes.Buffer
|
||
|
fmt.Fprint(&buf, x)
|
||
|
template.HTMLEscape(w, buf.Bytes())
|
||
|
}
|
||
|
|
||
|
func pathEscFmt(w io.Writer, format string, x ...interface{}) {
|
||
|
switch v := x[0].(type) {
|
||
|
case []byte:
|
||
|
template.HTMLEscape(w, v)
|
||
|
case string:
|
||
|
template.HTMLEscape(w, []byte(filepath.ToSlash(v)))
|
||
|
default:
|
||
|
var buf bytes.Buffer
|
||
|
fmt.Fprint(&buf, x)
|
||
|
template.HTMLEscape(w, buf.Bytes())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func htmlEscFmt(w io.Writer, format string, x ...interface{}) {
|
||
|
switch v := x[0].(type) {
|
||
|
case int:
|
||
|
template.HTMLEscape(w, []byte(strconv.Itoa(v)))
|
||
|
case []byte:
|
||
|
template.HTMLEscape(w, v)
|
||
|
case string:
|
||
|
template.HTMLEscape(w, []byte(v))
|
||
|
default:
|
||
|
var buf bytes.Buffer
|
||
|
fmt.Fprint(&buf, x)
|
||
|
template.HTMLEscape(w, buf.Bytes())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Template formatter for "padding" format.
|
||
|
func paddingFmt(w io.Writer, format string, x ...interface{}) {
|
||
|
for i := x[0].(int); i > 0; i-- {
|
||
|
fmt.Fprint(w, `<td width="25"></td>`)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Template formatter for "time" format.
|
||
|
func timeFmt(w io.Writer, format string, x ...interface{}) {
|
||
|
template.HTMLEscape(w, []byte(time.Unix(x[0].(int64)/1e9, 0).String()))
|
||
|
}
|
||
|
|
||
|
var fmap = template.FuncMap{
|
||
|
"repeat": strings.Repeat,
|
||
|
}
|
||
|
|
||
|
func readTemplateData(name, data string) *template.Template {
|
||
|
return template.Must(template.New(name).Funcs(fmap).Parse(data))
|
||
|
}
|
||
|
|
||
|
func readTemplateFile(name, path string) *template.Template {
|
||
|
return template.Must(template.New(name).Funcs(fmap).ParseFiles(path))
|
||
|
}
|
||
|
|
||
|
func applyTemplate(t *template.Template, name string, data interface{}) []byte {
|
||
|
var buf bytes.Buffer
|
||
|
if err := t.Execute(&buf, data); err != nil {
|
||
|
log.Printf("%s.Execute: %s", name, err)
|
||
|
}
|
||
|
return buf.Bytes()
|
||
|
}
|
||
|
|
||
|
type Info struct {
|
||
|
Find string
|
||
|
Best *DirEntry
|
||
|
Dirs *DirList
|
||
|
}
|
||
|
|
||
|
type GodocDir struct {
|
||
|
pkg *Directory
|
||
|
cmd *Directory
|
||
|
gopath []*Directory
|
||
|
}
|
||
|
|
||
|
func NewSourceDir(goroot string) *GodocDir {
|
||
|
pkgPath := filepath.Join(goroot, "src/pkg")
|
||
|
_, err := os.Stat(pkgPath)
|
||
|
var cmd *Directory
|
||
|
if err != nil {
|
||
|
pkgPath = filepath.Join(goroot, "src")
|
||
|
} else {
|
||
|
cmd = newDirectory(filepath.Join(goroot, "src", "cmd"), nil, -1)
|
||
|
}
|
||
|
pkg := newDirectory(pkgPath, nil, -1)
|
||
|
ctx := build.Default
|
||
|
ctx.GOROOT = ""
|
||
|
var gopath []*Directory
|
||
|
for _, v := range ctx.SrcDirs() {
|
||
|
gopath = append(gopath, newDirectory(v, nil, -1))
|
||
|
}
|
||
|
return &GodocDir{pkg, cmd, gopath}
|
||
|
}
|
||
|
|
||
|
func (dir *GodocDir) FindInfo(name string) *Info {
|
||
|
max1, best1, list1 := FindDir(dir.pkg, name)
|
||
|
max2, best2, list2 := FindDir(dir.cmd, name)
|
||
|
var maxHeight int
|
||
|
if max1 >= max2 {
|
||
|
maxHeight = max1
|
||
|
} else {
|
||
|
maxHeight = max2
|
||
|
}
|
||
|
var best *DirEntry
|
||
|
if best1 != nil {
|
||
|
best = best1
|
||
|
if best2 != nil {
|
||
|
list2 = append(list2, *best2)
|
||
|
}
|
||
|
} else {
|
||
|
best = best2
|
||
|
}
|
||
|
var list []DirEntry
|
||
|
list = append(list, list1...)
|
||
|
list = append(list, list2...)
|
||
|
for _, v := range dir.gopath {
|
||
|
max3, best3, list3 := FindDir(v, name)
|
||
|
if max3 > maxHeight {
|
||
|
maxHeight = max3
|
||
|
}
|
||
|
if best == nil {
|
||
|
best = best3
|
||
|
}
|
||
|
list = append(list, list3...)
|
||
|
}
|
||
|
return &Info{name, best, &DirList{maxHeight, list}}
|
||
|
}
|
||
|
|
||
|
func FindDir(dir *Directory, pkgname string) (maxHeight int, best *DirEntry, list []DirEntry) {
|
||
|
if dir == nil {
|
||
|
return
|
||
|
}
|
||
|
dirList := dir.listing(true)
|
||
|
max := len(dirList.List)
|
||
|
maxHeight = dirList.MaxHeight
|
||
|
|
||
|
for i := 0; i < max; i++ {
|
||
|
name := dirList.List[i].Name
|
||
|
path := filepath.ToSlash(dirList.List[i].Path)
|
||
|
if name == pkgname || path == pkgname {
|
||
|
best = &dirList.List[i]
|
||
|
} else if strings.Contains(path, pkgname) {
|
||
|
list = append(list, dirList.List[i])
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func appendList(list1, list2 []DirEntry) []DirEntry {
|
||
|
list := list1
|
||
|
max := len(list2)
|
||
|
for i := 0; i < max; i++ {
|
||
|
list = append(list, list2[i])
|
||
|
}
|
||
|
return list
|
||
|
}
|
||
|
|
||
|
func NewListInfo(root string) *Info {
|
||
|
dir := newDirectory(root, nil, -1)
|
||
|
if dir == nil {
|
||
|
return nil
|
||
|
}
|
||
|
return &Info{"", nil, dir.listing(true)}
|
||
|
}
|
||
|
|
||
|
func FindPkgInfo(root string, pkgname string) *Info {
|
||
|
dir := newDirectory(root, nil, -1)
|
||
|
if dir == nil {
|
||
|
return nil
|
||
|
}
|
||
|
dirList := dir.listing(true)
|
||
|
if pkgname == "*" {
|
||
|
return &Info{pkgname, nil, dirList}
|
||
|
}
|
||
|
var best DirEntry
|
||
|
var list []DirEntry
|
||
|
max := len(dirList.List)
|
||
|
for i := 0; i < max; i++ {
|
||
|
name := dirList.List[i].Name
|
||
|
path := filepath.ToSlash(dirList.List[i].Path)
|
||
|
if name == pkgname || path == pkgname {
|
||
|
best = dirList.List[i]
|
||
|
} else if strings.Contains(path, pkgname) {
|
||
|
list = append(list, dirList.List[i])
|
||
|
}
|
||
|
}
|
||
|
return &Info{pkgname, &best, &DirList{dirList.MaxHeight, list}}
|
||
|
}
|
||
|
|
||
|
func (info *Info) GetPkgList(name, templateData string) []byte {
|
||
|
data := readTemplateData(name, templateData)
|
||
|
return applyTemplate(data, "pkglist", info)
|
||
|
}
|
||
|
|
||
|
var listHTML = `<!-- Golang Package List -->
|
||
|
<p class="detail">
|
||
|
Need more packages? The
|
||
|
<a href="http://godashboard.appspot.com/package">Package Dashboard</a>
|
||
|
provides a list of <a href="/cmd/goinstall/">goinstallable</a> packages.
|
||
|
</p>
|
||
|
<h2 id="Subdirectories">Subdirectories</h2>
|
||
|
<p>
|
||
|
{{with .Dirs}}
|
||
|
<p>
|
||
|
<table class="layout">
|
||
|
<tr>
|
||
|
<th align="left" colspan="{{html .MaxHeight}}">Name</th>
|
||
|
<td width="25"> </td>
|
||
|
<th align="left">Synopsis</th>
|
||
|
</tr>
|
||
|
{{range .List}}
|
||
|
<tr>
|
||
|
{{repeat "<td width=\"25\"></td>" .Depth}}
|
||
|
<td align="left" colspan="{{html .Height}}"><a href="{{.Path}}">{{html .Name}}</a></td>
|
||
|
<td></td>
|
||
|
<td align="left">{{html .Synopsis}}</td>
|
||
|
</tr>
|
||
|
{{end}}
|
||
|
</table>
|
||
|
</p>
|
||
|
{{end}}`
|
||
|
|
||
|
var listText = `$list
|
||
|
{{with .Dirs}}
|
||
|
{{range .List}}{{.Path }}
|
||
|
{{end}}
|
||
|
{{end}}`
|
||
|
|
||
|
var listLite = `$list{{with .Dirs}}{{range .List}},{{.Path}}{{end}}{{end}}`
|
||
|
|
||
|
var findHTML = `<!-- Golang Package List -->
|
||
|
<p class="detail">
|
||
|
Need more packages? The
|
||
|
<a href="http://godashboard.appspot.com/package">Package Dashboard</a>
|
||
|
provides a list of <a href="/cmd/goinstall/">goinstallable</a> packages.
|
||
|
</p>
|
||
|
<h2 id="Subdirectories">Subdirectories</h2>
|
||
|
<table class="layout">
|
||
|
<tr>
|
||
|
<th align="left">Best</th>
|
||
|
<td width="25"> </td>
|
||
|
<th align="left">Synopsis</th>
|
||
|
{{with .Best}}
|
||
|
<tr>
|
||
|
<td align="left"><a href="{{html .Path}}">{{.Path}}</a></td>
|
||
|
<td></td>
|
||
|
<td align="left">{{html .Synopsis}}</td>
|
||
|
</tr>
|
||
|
{{end}}
|
||
|
{{with .Dirs}}
|
||
|
<tr>
|
||
|
<th align="left">Match</th>
|
||
|
<td width="25"> </td>
|
||
|
<th align="left">Synopsis</th>
|
||
|
</tr>
|
||
|
{{range .List}}
|
||
|
<tr>
|
||
|
<td align="left"><a href="{{html .Path}}">{{.Path}}</a></td>
|
||
|
<td></td>
|
||
|
<td align="left">{{html .Synopsis}}</td>
|
||
|
</tr>
|
||
|
{{end}}
|
||
|
</table>
|
||
|
</p>
|
||
|
{{end}}`
|
||
|
|
||
|
var findText = `$best
|
||
|
{{with .Best}}{{.Path}}{{end}}
|
||
|
$list
|
||
|
{{with .Dirs}}{{range .List}}{{.Path}}
|
||
|
{{end}}{{end}}`
|
||
|
|
||
|
var findLite = `$find,{{with .Best}}{{.Path}}{{end}}{{with .Dirs}}{{range .List}},{{.Path}}{{end}}{{end}}`
|