@ 261101.7

261101.1 · 0ad750f7e905ca3494043be38b127f94a06eb874

server.go splits by purpose

server.go held the server's identity, request guarding, the index, tree pages and page rendering. Each now has a file whose name says what is in it: about.go, guard.go, index.go, treepage.go and page.go. bugOff joins bug.go. server.go keeps the server and tree types, mounting, the embedded assets, the handler and the stop button.

Declarations moved unchanged, as a sorted comparison of every line outside the import blocks confirms. Follow them with git log --follow or blame -C.

reading declarations

Changes to about.go. All files

about.go

new file mode 100644@@ -0,0 +1,135 @@+package main++import (+	"fmt"+	"net/http"+	"net/netip"+	"os"+	"os/exec"+	"path/filepath"+	"time"+)++// about describes the server for its index: where it listens, since when,+// and the binary it runs, so the index can tell when a rebuild has left it+// behind. Tests leave it zero.+type about struct {+	ep      netip.AddrPort+	started time.Time+	exe     string+	exeInfo os.FileInfo+}++func newAbout(ep netip.AddrPort) about {+	a := about{ep: ep, started: time.Now()}+	if exe, err := os.Executable(); err == nil {+		if info, err := os.Stat(exe); err == nil {+			a.exe, a.exeInfo = exe, info+		}+	}+	return a+}++// stale reports whether the binary on disk is no longer the one running.+func (a about) stale() bool {+	if a.exeInfo == nil {+		return false+	}+	info, err := os.Stat(a.exe)+	return err != nil || !os.SameFile(info, a.exeInfo) || !info.ModTime().Equal(a.exeInfo.ModTime())+}++// restartCmd is the command that moves this server's trees to the new+// binary; with no address it moves every server its binary started. It+// names the binary by its path when the name on PATH leads to another,+// eg the package's, which would leave this server alone.+func (a about) restartCmd() string {+	if a.exe != "" {+		p, err := exec.LookPath(progName)+		if err == nil {+			p, err = filepath.EvalSymlinks(p)+		}+		if err != nil || p != a.exe {+			return tildePath(a.exe) + " --restart"+		}+	}+	return progName + " --restart"+}++// status is the index header's account of the server's age. The build+// time is on every page, above the header.+func (a about) status() string {+	if a.started.IsZero() {+		return ""+	}+	return "up since " + clock(a.started)+}++// built is when the running binary was written, read once at start, so+// each page can say which build served it. debugOn, set by --debug,+// puts it above the header, and on the index, each server's build and+// start and when each lock was written. Without it they stay hidden, as+// they serve debugging alone.+var debugOn bool++var buildCommit = buildRev()++var built = func() time.Time {+	exe, err := os.Executable()+	if err != nil {+		return time.Time{}+	}+	info, err := os.Stat(exe)+	if err != nil {+		return time.Time{}+	}+	return info.ModTime()+}()++// versionLine is --version's account of the build, in the words of the+// stamp, with the full date since a build may be old.+func versionLine(t time.Time, commit string) string {+	when := "at an unknown time"+	if !t.IsZero() {+		when = t.Format("2006-01-02 15:04")+	}+	return progName + " built " + when + ", from " + commit+}++// stamp is the build as a page shows it: the time of day, the seconds+// since, which live.js counts on from Millis, the commit, and how long+// the page took to render.+type stamp struct {+	Clock    string+	Millis   int64+	Ago      int+	Commit   string+	Rendered string+}++type startKey struct{}++// rendered says how long the request has taken so far, in milliseconds+// with a decimal below 10, since most docs render in under one.+func rendered(r *http.Request) string {+	start, ok := r.Context().Value(startKey{}).(time.Time)+	if !ok {+		return ""+	}+	ms := float64(time.Since(start).Microseconds()) / 1000+	if ms < 10 {+		return fmt.Sprintf("%.1f ms", ms)+	}+	return fmt.Sprintf("%.0f ms", ms)+}++// clock gives the time of day, with the date when it is not today.+func clock(t time.Time) string {+	if t.IsZero() {+		return ""+	}+	if y, m, d := t.Date(); y == time.Now().Year() && m == time.Now().Month() && d == time.Now().Day() {+		return t.Format("15:04")+	}+	return t.Format("Jan 2 15:04")+}