зеркало из https://github.com/CryptoPro/go.git
- properly align package synopses
(this was surprisingly hard to get right in HTML) - show modification times in source directory listings - various tweaks R=rsc http://go/go-review/1024024
This commit is contained in:
Родитель
40a7db3ee9
Коммит
202ede1240
|
@ -184,7 +184,6 @@ a.noline {
|
||||||
table.layout {
|
table.layout {
|
||||||
border-width: 0px;
|
border-width: 0px;
|
||||||
border-spacing: 0px;
|
border-spacing: 0px;
|
||||||
border-width: 0px;
|
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,10 @@
|
||||||
<table class="layout">
|
<table class="layout">
|
||||||
<tr>
|
<tr>
|
||||||
<th align="left">File</th>
|
<th align="left">File</th>
|
||||||
<th width="100" align="right">Size</th>
|
<td width="25"> </td>
|
||||||
|
<th align="right">Bytes</th>
|
||||||
|
<td width="25"> </td>
|
||||||
|
<th align="left">Modified</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href=".." class="noline">..</a></td>
|
<td><a href=".." class="noline">..</a></td>
|
||||||
|
@ -16,7 +19,10 @@
|
||||||
{.repeated section @}
|
{.repeated section @}
|
||||||
<tr>
|
<tr>
|
||||||
<td align="left"><a href="{Name|html}" class="noline">{Name|html}</a></td>
|
<td align="left"><a href="{Name|html}" class="noline">{Name|html}</a></td>
|
||||||
|
<td></td>
|
||||||
<td align="right">{Size|html}</td>
|
<td align="right">{Size|html}</td>
|
||||||
|
<td></td>
|
||||||
|
<td align="left">{Mtime_ns|time}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{.end}
|
{.end}
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
<table class="layout">
|
|
||||||
<tr><td colspan="2"><a href="{Path|path}">{Name|html}</a></td><td width="10"></td><td>{Text|html}</td></tr>
|
|
||||||
{.repeated section Dirs}
|
|
||||||
<tr><td width="25"></td><td>{@|dir}</td></tr>
|
|
||||||
{.end}
|
|
||||||
</table>
|
|
|
@ -97,7 +97,7 @@
|
||||||
<li class="navhead">Programming</li>
|
<li class="navhead">Programming</li>
|
||||||
<li><a href="/cmd" class="noline">Command documentation</a></li>
|
<li><a href="/cmd" class="noline">Command documentation</a></li>
|
||||||
<li><a href="/pkg" class="noline">Package documentation</a></li>
|
<li><a href="/pkg" class="noline">Package documentation</a></li>
|
||||||
<li><a href="/src" class="noline">Sources</a></li>
|
<li><a href="/src" class="noline">Source files</a></li>
|
||||||
|
|
||||||
<li class="blank"> </li>
|
<li class="blank"> </li>
|
||||||
<li class="navhead">Help</li>
|
<li class="navhead">Help</li>
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
|
|
||||||
<li class="blank"> </li>
|
<li class="blank"> </li>
|
||||||
<li class="navhead">Last update</li>
|
<li class="navhead">Last update</li>
|
||||||
<li>{Timestamp|html}</li>
|
<li>{Timestamp|time}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -76,10 +76,22 @@
|
||||||
{.end}
|
{.end}
|
||||||
{.end}
|
{.end}
|
||||||
{.section Dirs}
|
{.section Dirs}
|
||||||
{.section Dirs}
|
<h2>Subdirectories</h2>
|
||||||
<h2>Subdirectories</h2>
|
<p>
|
||||||
{.repeated section @}
|
<table class="layout">
|
||||||
{@|dir}
|
<tr>
|
||||||
{.end}
|
<th align="left" colspan="{MaxHeight|html}">Name</th>
|
||||||
|
<td width="25"> </td>
|
||||||
|
<th align="left">Synopsis</th>
|
||||||
|
</tr>
|
||||||
|
{.repeated section List}
|
||||||
|
<tr>
|
||||||
|
{Depth|padding}
|
||||||
|
<td align="left" colspan="{Height|html}"><a href="{Path|html}" class="noline">{Name|html}<a></td>
|
||||||
|
<td></td>
|
||||||
|
<td align="left">{Synopsis|html}</td>
|
||||||
|
</tr>
|
||||||
{.end}
|
{.end}
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
{.end}
|
{.end}
|
||||||
|
|
|
@ -77,7 +77,7 @@ BUGS
|
||||||
|
|
||||||
SUBDIRECTORIES
|
SUBDIRECTORIES
|
||||||
|
|
||||||
{.repeated section @}
|
{.repeated section List}
|
||||||
{Name}
|
{Name}
|
||||||
{.end}
|
{.end}
|
||||||
{.end}
|
{.end}
|
||||||
|
|
|
@ -152,19 +152,20 @@ func firstSentence(s string) string {
|
||||||
// Package directories
|
// Package directories
|
||||||
|
|
||||||
type Directory struct {
|
type Directory struct {
|
||||||
|
Depth int;
|
||||||
Path string; // includes Name
|
Path string; // includes Name
|
||||||
Name string;
|
Name string;
|
||||||
Text string; // package documentation, if any
|
Text string; // package documentation, if any
|
||||||
Dirs []*Directory;
|
Dirs []*Directory; // subdirectories
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func newDirTree(path, name string, depth int) *Directory {
|
func newDirTree(path, name string, depth, maxDepth int) *Directory {
|
||||||
if depth <= 0 {
|
if depth >= maxDepth {
|
||||||
// return a dummy directory so that the parent directory
|
// return a dummy directory so that the parent directory
|
||||||
// doesn't get discarded just because we reached the max
|
// doesn't get discarded just because we reached the max
|
||||||
// directory depth
|
// directory depth
|
||||||
return &Directory{path, name, "", nil};
|
return &Directory{depth, path, name, "", nil};
|
||||||
}
|
}
|
||||||
|
|
||||||
list, _ := io.ReadDir(path); // ignore errors
|
list, _ := io.ReadDir(path); // ignore errors
|
||||||
|
@ -183,7 +184,14 @@ func newDirTree(path, name string, depth int) *Directory {
|
||||||
// no package documentation yet; take the first found
|
// no package documentation yet; take the first found
|
||||||
file, err := parser.ParseFile(pathutil.Join(path, d.Name), nil,
|
file, err := parser.ParseFile(pathutil.Join(path, d.Name), nil,
|
||||||
parser.ParseComments | parser.PackageClauseOnly);
|
parser.ParseComments | parser.PackageClauseOnly);
|
||||||
if err == nil && file.Name.Value == name && file.Doc != nil {
|
if err == nil &&
|
||||||
|
// Also accept fakePkgName, so we get synopses for commmands.
|
||||||
|
// Note: This may lead to incorrect results if there is a
|
||||||
|
// (left-over) "documentation" package somewhere in a package
|
||||||
|
// directory of different name, but this is very unlikely and
|
||||||
|
// against current conventions.
|
||||||
|
(file.Name.Value == name || file.Name.Value == fakePkgName) &&
|
||||||
|
file.Doc != nil {
|
||||||
// found documentation; extract a synopsys
|
// found documentation; extract a synopsys
|
||||||
text = firstSentence(doc.CommentText(file.Doc));
|
text = firstSentence(doc.CommentText(file.Doc));
|
||||||
}
|
}
|
||||||
|
@ -198,7 +206,7 @@ func newDirTree(path, name string, depth int) *Directory {
|
||||||
i := 0;
|
i := 0;
|
||||||
for _, d := range list {
|
for _, d := range list {
|
||||||
if isPkgDir(d) {
|
if isPkgDir(d) {
|
||||||
dd := newDirTree(pathutil.Join(path, d.Name), d.Name, depth-1);
|
dd := newDirTree(pathutil.Join(path, d.Name), d.Name, depth+1, maxDepth);
|
||||||
if dd != nil {
|
if dd != nil {
|
||||||
dirs[i] = dd;
|
dirs[i] = dd;
|
||||||
i++;
|
i++;
|
||||||
|
@ -214,21 +222,43 @@ func newDirTree(path, name string, depth int) *Directory {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Directory{path, name, text, dirs};
|
return &Directory{depth, path, name, text, dirs};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// newDirectory creates a new package directory tree with at most depth
|
// newDirectory creates a new package directory tree with at most maxDepth
|
||||||
// levels, anchored at root which is relative to goroot. The result tree
|
// levels, anchored at root which is relative to goroot. The result tree
|
||||||
// only contains directories that contain package files or that contain
|
// only contains directories that contain package files or that contain
|
||||||
// subdirectories containing package files (transitively).
|
// subdirectories containing package files (transitively).
|
||||||
//
|
//
|
||||||
func newDirectory(root string, depth int) *Directory {
|
func newDirectory(root string, maxDepth int) *Directory {
|
||||||
d, err := os.Lstat(root);
|
d, err := os.Lstat(root);
|
||||||
if err != nil || !isPkgDir(d) {
|
if err != nil || !isPkgDir(d) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
return newDirTree(root, d.Name, depth);
|
return newDirTree(root, d.Name, 0, maxDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) {
|
||||||
|
if dir != nil {
|
||||||
|
if !skipRoot {
|
||||||
|
c <- dir;
|
||||||
|
}
|
||||||
|
for _, d := range dir.Dirs {
|
||||||
|
d.walk(c, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (dir *Directory) iter(skipRoot bool) <-chan *Directory {
|
||||||
|
c := make(chan *Directory);
|
||||||
|
go func() {
|
||||||
|
dir.walk(c, skipRoot);
|
||||||
|
close(c);
|
||||||
|
}();
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -255,6 +285,93 @@ func (dir *Directory) lookup(path string) *Directory {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DirEntry describes a directory entry. The Depth and Height values
|
||||||
|
// are useful for presenting an entry in an indented fashion.
|
||||||
|
//
|
||||||
|
type DirEntry struct {
|
||||||
|
Depth int; // >= 0
|
||||||
|
Height int; // = DirList.MaxHeight - Depth, > 0
|
||||||
|
Path string; // includes Name, relative to DirList root
|
||||||
|
Name string;
|
||||||
|
Synopsis string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type DirList struct {
|
||||||
|
MaxHeight int; // directory tree height, > 0
|
||||||
|
List []DirEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// listing creates a (linear) directory listing from a directory tree.
|
||||||
|
// If skipRoot is set, the root directory itself is excluded from the list.
|
||||||
|
//
|
||||||
|
func (root *Directory) listing(skipRoot bool) *DirList {
|
||||||
|
if root == nil {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine number of entries n and maximum height
|
||||||
|
n := 0;
|
||||||
|
minDepth := 1<<30; // infinity
|
||||||
|
maxDepth := 0;
|
||||||
|
for d := range root.iter(skipRoot) {
|
||||||
|
n++;
|
||||||
|
if minDepth > d.Depth {
|
||||||
|
minDepth = d.Depth;
|
||||||
|
}
|
||||||
|
if maxDepth < d.Depth {
|
||||||
|
maxDepth = d.Depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maxHeight := maxDepth-minDepth+1;
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
list := make([]DirEntry, n);
|
||||||
|
i := 0;
|
||||||
|
for d := range root.iter(skipRoot) {
|
||||||
|
p := &list[i];
|
||||||
|
p.Depth = d.Depth - minDepth;
|
||||||
|
p.Height = maxHeight - p.Depth;
|
||||||
|
// the path is relative to root.Path - remove the root.Path
|
||||||
|
// prefix (the prefix should always be present but avoid
|
||||||
|
// crashes and check)
|
||||||
|
path := d.Path;
|
||||||
|
if strings.HasPrefix(d.Path, root.Path) {
|
||||||
|
path = d.Path[len(root.Path):len(d.Path)];
|
||||||
|
}
|
||||||
|
// remove trailing '/' if any - path must be relative
|
||||||
|
if len(path) > 0 && path[0] == '/' {
|
||||||
|
path = path[1:len(path)];
|
||||||
|
}
|
||||||
|
p.Path = path;
|
||||||
|
p.Name = d.Name;
|
||||||
|
p.Synopsis = d.Text;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DirList{maxHeight, list};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func listing(dirs []*os.Dir) *DirList {
|
||||||
|
list := make([]DirEntry, len(dirs)+1);
|
||||||
|
list[0] = DirEntry{0, 1, "..", "..", ""};
|
||||||
|
for i, d := range dirs {
|
||||||
|
p := &list[i+1];
|
||||||
|
p.Depth = 0;
|
||||||
|
p.Height = 1;
|
||||||
|
p.Path = d.Name;
|
||||||
|
p.Name = d.Name;
|
||||||
|
}
|
||||||
|
return &DirList{1, list};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Parsing
|
// Parsing
|
||||||
|
|
||||||
|
@ -438,15 +555,6 @@ func textFmt(w io.Writer, x interface{}, format string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Template formatter for "dir" format.
|
|
||||||
func dirFmt(w io.Writer, x interface{}, format string) {
|
|
||||||
_ = x.(*Directory); // die quickly if x has the wrong type
|
|
||||||
if err := dirsHtml.Execute(x, w); err != nil {
|
|
||||||
log.Stderrf("dirsHtml.Execute: %s", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func removePrefix(s, prefix string) string {
|
func removePrefix(s, prefix string) string {
|
||||||
if strings.HasPrefix(s, prefix) {
|
if strings.HasPrefix(s, prefix) {
|
||||||
return s[len(prefix):len(s)];
|
return s[len(prefix):len(s)];
|
||||||
|
@ -525,16 +633,32 @@ func infoSnippetFmt(w io.Writer, x interface{}, format string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Template formatter for "padding" format.
|
||||||
|
func paddingFmt(w io.Writer, x interface{}, format string) {
|
||||||
|
for i := x.(int); i > 0; i-- {
|
||||||
|
fmt.Fprint(w, `<td width="25"></td>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Template formatter for "time" format.
|
||||||
|
func timeFmt(w io.Writer, x interface{}, format string) {
|
||||||
|
// note: os.Dir.Mtime_ns is in uint64 in ns!
|
||||||
|
template.HtmlEscape(w, strings.Bytes(time.SecondsToLocalTime(int64(x.(uint64) / 1e9)).String()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var fmap = template.FormatterMap{
|
var fmap = template.FormatterMap{
|
||||||
"": textFmt,
|
"": textFmt,
|
||||||
"html": htmlFmt,
|
"html": htmlFmt,
|
||||||
"html-comment": htmlCommentFmt,
|
"html-comment": htmlCommentFmt,
|
||||||
"dir": dirFmt,
|
|
||||||
"path": pathFmt,
|
"path": pathFmt,
|
||||||
"link": linkFmt,
|
"link": linkFmt,
|
||||||
"infoClass": infoClassFmt,
|
"infoClass": infoClassFmt,
|
||||||
"infoLine": infoLineFmt,
|
"infoLine": infoLineFmt,
|
||||||
"infoSnippet": infoSnippetFmt,
|
"infoSnippet": infoSnippetFmt,
|
||||||
|
"padding": paddingFmt,
|
||||||
|
"time": timeFmt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -553,8 +677,7 @@ func readTemplate(name string) *template.Template {
|
||||||
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dirListHtml,
|
dirlistHtml,
|
||||||
dirsHtml,
|
|
||||||
godocHtml,
|
godocHtml,
|
||||||
packageHtml,
|
packageHtml,
|
||||||
packageText,
|
packageText,
|
||||||
|
@ -566,8 +689,7 @@ var (
|
||||||
func readTemplates() {
|
func readTemplates() {
|
||||||
// have to delay until after flags processing,
|
// have to delay until after flags processing,
|
||||||
// so that main has chdir'ed to goroot.
|
// so that main has chdir'ed to goroot.
|
||||||
dirListHtml = readTemplate("dirlist.html");
|
dirlistHtml = readTemplate("dirlist.html");
|
||||||
dirsHtml = readTemplate("dirs.html");
|
|
||||||
godocHtml = readTemplate("godoc.html");
|
godocHtml = readTemplate("godoc.html");
|
||||||
packageHtml = readTemplate("package.html");
|
packageHtml = readTemplate("package.html");
|
||||||
packageText = readTemplate("package.txt");
|
packageText = readTemplate("package.txt");
|
||||||
|
@ -583,7 +705,7 @@ func readTemplates() {
|
||||||
func servePage(c *http.Conn, title, query string, content []byte) {
|
func servePage(c *http.Conn, title, query string, content []byte) {
|
||||||
type Data struct {
|
type Data struct {
|
||||||
Title string;
|
Title string;
|
||||||
Timestamp string;
|
Timestamp uint64; // int64 to be compatible with os.Dir.Mtime_ns
|
||||||
Query string;
|
Query string;
|
||||||
Content []byte;
|
Content []byte;
|
||||||
}
|
}
|
||||||
|
@ -591,7 +713,7 @@ func servePage(c *http.Conn, title, query string, content []byte) {
|
||||||
_, ts := fsTree.get();
|
_, ts := fsTree.get();
|
||||||
d := Data{
|
d := Data{
|
||||||
Title: title,
|
Title: title,
|
||||||
Timestamp: time.SecondsToLocalTime(ts).String(),
|
Timestamp: uint64(ts)*1e9, // timestamp in ns
|
||||||
Query: query,
|
Query: query,
|
||||||
Content: content,
|
Content: content,
|
||||||
};
|
};
|
||||||
|
@ -756,8 +878,8 @@ func serveDirectory(c *http.Conn, r *http.Request, path string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer;
|
var buf bytes.Buffer;
|
||||||
if err := dirListHtml.Execute(list, &buf); err != nil {
|
if err := dirlistHtml.Execute(list, &buf); err != nil {
|
||||||
log.Stderrf("dirListHtml.Execute: %s", err);
|
log.Stderrf("dirlistHtml.Execute: %s", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
servePage(c, "Directory " + path, "", buf.Bytes());
|
servePage(c, "Directory " + path, "", buf.Bytes());
|
||||||
|
@ -818,7 +940,7 @@ const fakePkgName = "documentation"
|
||||||
|
|
||||||
type PageInfo struct {
|
type PageInfo struct {
|
||||||
PDoc *doc.PackageDoc; // nil if no package found
|
PDoc *doc.PackageDoc; // nil if no package found
|
||||||
Dirs *Directory; // nil if no directory information found
|
Dirs *DirList; // nil if no directory information found
|
||||||
IsPkg bool; // false if this is not documenting a real package
|
IsPkg bool; // false if this is not documenting a real package
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -885,7 +1007,7 @@ func (h *httpHandler) getPageInfo(path string) PageInfo {
|
||||||
dir = newDirectory(dirname, 1);
|
dir = newDirectory(dirname, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PageInfo{pdoc, dir, h.isPkg};
|
return PageInfo{pdoc, dir.listing(true), h.isPkg};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче