dmitri.shuralyov.com/website/gido

Add /-/packages page that lists all packages.

Sometimes it's helpful to be able to see a list of all packages (and
their number of open issues) at a glance.

Since they're all on one page, it's easy to use the browser's search
functionality to find a specific package or parent folder.

If Accept header is set to exactly "application/json", respond in JSON
format.
dmitshur committed 6 years ago commit dcee4de78e6ca89e867023847cd62ee6913b57a7
Showing partial commit. Full Commit
Collapse all
main.go
@@ -1,10 +1,11 @@
// gido is the command that powers the https://goissues.org website.
package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
@@ -116,10 +117,15 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) error {
	if req.URL.Path == "/assets" || strings.HasPrefix(req.URL.Path, "/assets/") {
		h.assetsHandler.ServeHTTP(w, req)
		return nil
	}

	// Handle "/-/packages".
	if req.URL.Path == "/-/packages" {
		return h.ServePackages(w, req)
	}

	// Handle "/..." URLs.
	if canonicalPath := path.Clean(req.URL.Path); req.URL.Path != canonicalPath {
		// Redirect to canonical path (no trailing slash, etc.).
		if req.URL.RawQuery != "" {
			canonicalPath += "?" + req.URL.RawQuery
@@ -139,11 +145,12 @@ const htmlPart1, htmlPart2, htmlPart3 = `<html>
		<link href="/assets/style.css" rel="stylesheet" type="text/css">
	</head>
	<body style="margin: 0; position: relative;">
		<header style="background-color: hsl(209, 51%, 92%);">
			<div style="max-width: 800px; margin: 0 auto 0 auto; padding: 0 15px 0 15px;">
				<a class="black" href="/"><strong style="padding: 15px 0 15px 0; display: inline-block;">Go Issues</strong></a>
				<a class="black" href="/"                                      ><strong style="padding: 15px 0 15px 0; display: inline-block;">Go Issues</strong></a>
				<a class="black" href="/-/packages" style="padding-left: 30px;"><span   style="padding: 15px 0 15px 0; display: inline-block;">Packages</span></a>
			</div>
		</header>

		<main style="max-width: 800px; margin: 0 auto 0 auto; padding: 0 15px 120px 15px;">`, `</main>

@@ -154,10 +161,11 @@ const htmlPart1, htmlPart2, htmlPart3 = `<html>
		</footer>
	</body>
</html>
`

// ServeIndex serves the index page.
func (h *handler) ServeIndex(w http.ResponseWriter, req *http.Request) error {
	if req.Method != http.MethodGet {
		return httperror.Method{Allowed: []string{http.MethodGet}}
	}

@@ -185,15 +193,15 @@ func (h *handler) ServeIndex(w http.ResponseWriter, req *http.Request) error {
			using your browser's address bar (<kbd>⌘Cmd</kbd>+<kbd>L</kbd> or <kbd>Ctrl</kbd>+<kbd>L</kbd>).</p>

			<p>Supported import paths include:</p>

			<ul>
			<li><a href="https://golang.org/pkg/#stdlib">Standard library</a> (e.g., <code>io</code>, <code>net/http</code>, etc.),</li>
			<li><a href="https://golang.org/pkg/#subrepo">Sub-repositories</a> (i.e., <code>golang.org/x/...</code>).</li>
			<li><a href="/-/packages#stdlib">Standard library</a> (e.g., <code>io</code>, <code>net/http</code>, etc.),</li>
			<li><a href="/-/packages#subrepo">Sub-repositories</a> (i.e., <code>golang.org/x/...</code>).</li>
			</ul>

			<p>Import paths of 3rd party packages (e.g., <code>github.com/...</code>) are not supported.</p>
			<p>Import paths of 3rd party packages (e.g., <code>github.com/...</code>) are not supported at this time.</p>

			<p>It's a simple website with a narrow scope. Enjoy. ʕ◔ϖ◔ʔ</p>
		`)
	if err != nil {
		return err
@@ -226,13 +234,97 @@ func (h *handler) ServeIndex(w http.ResponseWriter, req *http.Request) error {

	_, err = io.WriteString(w, htmlPart3)
	return err
}

// ServePackages serves a list of all known packages.
func (h *handler) ServePackages(w http.ResponseWriter, req *http.Request) error {
	if req.Method != http.MethodGet {
		return httperror.Method{Allowed: []string{http.MethodGet}}
	}

	// Gather all packages in sorted order.
	h.s.PackageIssuesMu.RLock()
	pis := h.s.PackageIssues
	h.s.PackageIssuesMu.RUnlock()
	var stdlib, subrepo []pkg
	for _, p := range h.s.Packages {
		switch isStandard(p) {
		case true:
			stdlib = append(stdlib, pkg{
				Path: p,
				Open: len(pis[p].Open),
			})
		case false:
			subrepo = append(subrepo, pkg{
				Path: p,
				Open: len(pis[p].Open),
			})
		}
	}

	if req.Header.Get("Accept") == "application/json" {
		w.Header().Set("Content-Type", "application/json")
		e := json.NewEncoder(w)
		e.SetIndent("", "\t")
		err := e.Encode(append(stdlib, subrepo...)) // TODO: Measure if slow, optimize if needed.
		return err
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	_, err := io.WriteString(w, htmlPart1)
	if err != nil {
		return err
	}
	_, err = w.Write(h.analyticsHTML)
	if err != nil {
		return err
	}
	_, err = io.WriteString(w, htmlPart2)
	if err != nil {
		return err
	}

	// Render the stdlib section.
	_, err = io.WriteString(w, `<h3 id="stdlib" style="margin-top: 30px;">Standard Library Packages</h3>`)
	if err != nil {
		return err
	}
	err = renderTable(w, stdlib)
	if err != nil {
		return err
	}

	// Render the subrepo section.
	_, err = io.WriteString(w, `<h3 id="subrepo" style="margin-top: 30px;">Sub-repository Packages</h3>`)
	if err != nil {
		return err
	}
	err = renderTable(w, subrepo)
	if err != nil {
		return err
	}

	// Write note about other issues.
	_, err = io.WriteString(w, `<h3 id="other" style="margin-top: 30px;">Other Issues</h3>

		<p>
			Issues from the <a href="https://golang.org/issues">Go issue tracker</a>
			that don't fit into any existing Go package
			can be seen under <a href="/other">other</a>.
		</p>`)
	if err != nil {
		return err
	}

	_, err = io.WriteString(w, htmlPart3)
	return err
}

type pkg struct {
	Path string
	Open int
	Path string `json:"ImportPath"`
	Open int    `json:"OpenIssues"`
}

func renderTable(w io.Writer, pkgs []pkg) error {
	_, err := io.WriteString(w, `<table class="table table-sm">
		<thead>
@@ -300,14 +392,16 @@ func (h *handler) ServeIssues(w http.ResponseWriter, req *http.Request, pkg stri
	if err != nil {
		return err
	}
	heading := htmlg.NodeComponent{
		Type: html.ElementNode, Data: atom.H2.String(),
		Attr:       []html.Attribute{{Key: atom.Style.String(), Val: "margin-top: 30px;"}},
		FirstChild: htmlg.Text(pkg),
	}
	title := pkg + ": "
	if pkg == otherPackages {
		heading.Data, heading.FirstChild = atom.H3.String(), htmlg.Text("Other Go Issues")
		title = ""
	}
	newIssue := htmlg.NodeComponent{
		Type: html.ElementNode, Data: atom.Div.String(),
		Attr:       []html.Attribute{{Key: atom.Style.String(), Val: "text-align: right;"}},