// Copyright 2011-2015 visualfc . 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, "\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, ``) } } // 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 = `

Need more packages? The Package Dashboard provides a list of goinstallable packages.

Subdirectories

{{with .Dirs}}

{{range .List}} {{repeat "" .Depth}} {{end}}
Name   Synopsis
{{html .Name}} {{html .Synopsis}}

{{end}}` var listText = `$list {{with .Dirs}} {{range .List}}{{.Path }} {{end}} {{end}}` var listLite = `$list{{with .Dirs}}{{range .List}},{{.Path}}{{end}}{{end}}` var findHTML = `

Need more packages? The Package Dashboard provides a list of goinstallable packages.

Subdirectories

{{with .Best}} {{end}} {{with .Dirs}} {{range .List}} {{end}}
Best   Synopsis
{{.Path}} {{html .Synopsis}}
Match   Synopsis
{{.Path}} {{html .Synopsis}}

{{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}}`