pkgroot = flag.String("pkgroot", "src/pkg", "root package source directory (if unrooted, relative to goroot)");
tmplroot = flag.String("tmplroot", "lib/godoc", "root template directory (if unrooted, relative to goroot)");
- // periodic sync
- syncTime RWValue; // time of last sync
-
// layout control
tabwidth = flag.Int("tabwidth", 4, "tab width");
)
goroot = "/home/r/go-release/go";
}
flag.StringVar(&goroot, "goroot", goroot, "Go root directory");
- syncTime.set(nil); // have a reasonable initial value (time is shown on web page)
}
}
+// ----------------------------------------------------------------------------
+// Directory trees
+
+type Directory struct {
+ Path string; // including Name
+ Name string;
+ Subdirs []*Directory
+}
+
+
+func newDirTree0(path, name string) *Directory {
+ list, _ := io.ReadDir(path); // ignore errors
+ // determine number of subdirectories n
+ n := 0;
+ for _, d := range list {
+ if isPkgDir(d) {
+ n++;
+ }
+ }
+ // create Directory node
+ var subdirs []*Directory;
+ if n > 0 {
+ subdirs = make([]*Directory, n);
+ i := 0;
+ for _, d := range list {
+ if isPkgDir(d) {
+ subdirs[i] = newDirTree0(pathutil.Join(path, d.Name), d.Name);
+ i++;
+ }
+ }
+ }
+ if strings.HasPrefix(path, "src/") {
+ path = path[len("src/") : len(path)];
+ }
+ return &Directory{path, name, subdirs};
+}
+
+
+func newDirTree(root string) *Directory {
+ d, err := os.Lstat(root);
+ if err != nil {
+ log.Stderrf("%v", err);
+ return nil;
+ }
+ if !isPkgDir(d) {
+ log.Stderrf("not a package directory: %s", d.Name);
+ return nil;
+ }
+ return newDirTree0(root, d.Name);
+}
+
+
// ----------------------------------------------------------------------------
// Parsing
}
+// 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);
+ }
+}
+
+
// Template formatter for "link" format.
func linkFmt(w io.Writer, x interface{}, format string) {
type Positioner interface {
"": textFmt,
"html": htmlFmt,
"html-comment": htmlCommentFmt,
+ "dir": dirFmt,
"link": linkFmt,
"infoClass": infoClassFmt,
"infoLine": infoLineFmt,
var (
+ dirsHtml,
godocHtml,
packageHtml,
packageText,
func readTemplates() {
// have to delay until after flags processing,
// so that main has chdir'ed to goroot.
+ dirsHtml = readTemplate("dirs.html");
godocHtml = readTemplate("godoc.html");
packageHtml = readTemplate("package.html");
packageText = readTemplate("package.txt");
// ----------------------------------------------------------------------------
// Generic HTML wrapper
+var pkgTree RWValue; // *Directory tree of packages, updated with each sync
+
func servePage(c *http.Conn, title, query string, content []byte) {
type Data struct {
Title string;
Content []byte;
}
- _, ts := syncTime.get();
+ _, ts := pkgTree.get();
d := Data{
Title: title,
Timestamp: time.SecondsToLocalTime(ts).String(),
}
+// ----------------------------------------------------------------------------
+// Directory tree
+
+// TODO(gri): Temporary - integrate with package serving.
+
+func serveTree(c *http.Conn, r *http.Request) {
+ dir, _ := pkgTree.get();
+
+ var buf bytes.Buffer;
+ dirFmt(&buf, dir, "");
+
+ servePage(c, "Package tree", "", buf.Bytes());
+}
+
+
// ----------------------------------------------------------------------------
// Search
if index, timestamp := searchIndex.get(); index != nil {
result.Query = query;
result.Hit, result.Alt = index.(*Index).Lookup(query);
- _, ts := syncTime.get();
+ _, ts := pkgTree.get();
result.Accurate = timestamp >= ts;
result.Legend = &infoClasses;
}
func registerPublicHandlers(mux *http.ServeMux) {
mux.Handle(Pkg, http.HandlerFunc(servePkg));
+ mux.Handle("/tree", http.HandlerFunc(serveTree)); // TODO(gri): integrate with package serving
mux.Handle("/search", http.HandlerFunc(search));
mux.Handle("/", http.HandlerFunc(serveFile));
}
// Indexing goroutine.
func indexer() {
for {
- _, ts := syncTime.get();
+ _, ts := pkgTree.get();
if _, timestamp := searchIndex.get(); timestamp < ts {
// index possibly out of date - make a new one
// (could use a channel to send an explicit signal
args := []string{"/bin/sh", "-c", *syncCmd};
switch exec(c, args) {
case 0:
- // sync succeeded and some files have changed
- syncTime.set(nil);
+ // sync succeeded and some files have changed;
+ // update package tree
+ pkgTree.set(newDirTree(*pkgroot));
fallthrough;
case 1:
- // sync failed because no files changed
- // don't change the sync time
+ // sync failed because no files changed;
+ // don't change the package tree
syncDelay.set(*syncMin); // revert to regular sync schedule
default:
// sync failed because of an error - back off exponentially, but try at least once a day
http.Handle("/debug/sync", http.HandlerFunc(dosync));
}
- // The server may have been restarted; always wait 1sec to
- // give the forking server a chance to shut down and release
- // the http port.
- time.Sleep(1e9);
+ // Compute package tree with corresponding timestamp.
+ pkgTree.set(newDirTree(*pkgroot));
// Start sync goroutine, if enabled.
if *syncCmd != "" && *syncMin > 0 {
// Start indexing goroutine.
go indexer();
+ // The server may have been restarted; always wait 1sec to
+ // give the forking server a chance to shut down and release
+ // the http port.
+ // TODO(gri): Do we still need this?
+ time.Sleep(1e9);
+
// Start http server.
if err := http.ListenAndServe(*httpaddr, handler); err != nil {
log.Exitf("ListenAndServe %s: %v", *httpaddr, err);
return;
}
+ // Command line mode.
+ // No package tree; set it to nil so we have a reasonable time stamp.
+ pkgTree.set(nil);
+
if *html {
packageText = packageHtml;
parseerrorText = parseerrorHtml;