pkgsite/internal/frontend
Julie Qiu 6f80375784 internal/dcensus: add Latency
There are several places where we need to compute the latency
measurement for recording an opencensus measurement.

A dcensus.Latency function is added so that
we can ensure latency is being calculated in the same way for all
measurements.

Change-Id: Ic138f29106f88c9c468084816d8856e47bbd7cc2
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/247359
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
2021-06-12 01:54:21 +00:00
..
404.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
404_test.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
badge.go content,internal: prepare badge page for dark theme 2021-06-09 13:25:16 +00:00
badge_test.go content,internal: update pages to use new base layout 2021-06-07 15:09:35 +00:00
breadcrumb.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
breadcrumb_test.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
debug.go all: go fmt ./... 2021-02-20 03:30:25 +00:00
debug_test.go all: go fmt ./... 2021-02-20 03:30:25 +00:00
details.go content,internal: update pages to use new base layout 2021-06-07 15:09:35 +00:00
directory.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
directory_test.go internal/postgres,etc: finish MustInsertModule cleanup 2021-04-08 16:30:30 +00:00
doc.go internal/doc: rename RenderParts to Render 2021-04-22 12:18:18 +00:00
doc_test.go internal: rename tc to test 2020-11-20 17:06:53 +00:00
fetch.go internal/dcensus: add Latency 2021-06-12 01:54:21 +00:00
fetch_test.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
goldmark.go internal/frontend: preserve all heading text content in README outline 2021-06-09 23:42:28 +00:00
header.go internal/postgres: use pgx for tests 2021-03-30 16:41:56 +00:00
header_test.go internal/frontend: fix pageTitle for v2+ module versions 2020-12-04 17:12:40 +00:00
imports.go internal/postgres: read only one doc source from DB 2021-04-29 21:01:19 +00:00
imports_test.go internal/frontend: tweak display text and log error for importedby 2021-04-29 19:40:19 +00:00
latest_version.go internal/frontend: add additional stats in serveUnitPage stack 2021-04-19 22:54:32 +00:00
latest_version_test.go internal/frontend,etc.: optimize away second GetUnitMeta call 2021-04-02 12:11:48 +00:00
license.go internal/postgres: read only one doc source from DB 2021-04-29 21:01:19 +00:00
license_test.go internal/postgres,etc: finish MustInsertModule cleanup 2021-04-08 16:30:30 +00:00
main.go internal: change GetSymbolHistoryForBuildContext output 2021-05-04 02:15:14 +00:00
main_test.go internal/postgres,etc: finish MustInsertModule cleanup 2021-04-08 16:30:30 +00:00
overview.go internal/frontend: rewrite img links to GitHub blobs 2021-03-23 17:42:50 +00:00
overview_test.go internal/frontend: rewrite img links to GitHub blobs 2021-03-23 17:42:50 +00:00
package_test.go internal/postgres,etc: finish MustInsertModule cleanup 2021-04-08 16:30:30 +00:00
paginate.go all: rename module to golang.org/x/pkgsite 2020-04-23 16:18:43 +00:00
paginate_test.go internal: rename tc to test 2020-11-20 17:06:53 +00:00
playground.go cmd,internal/frontend: remove legacy playground code 2021-04-20 17:14:51 +00:00
playground_test.go cmd,internal/frontend: remove legacy playground code 2021-04-20 17:14:51 +00:00
readme.go internal/frontend: update goldmark heading extraction 2021-05-18 15:33:52 +00:00
readme_test.go internal/frontend: preserve all heading text content in README outline 2021-06-09 23:42:28 +00:00
redirect.go internal/frontend: refactor stdlibPathForShortcut 2021-01-11 21:47:20 +00:00
search.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
search_test.go internal/postgres,etc: finish MustInsertModule cleanup 2021-04-08 16:30:30 +00:00
section.go internal/frontend: recognize "golang.org" prefix 2020-03-27 16:46:42 -04:00
section_test.go internal: rename tc to test 2020-11-20 17:06:53 +00:00
server.go internal/frontend: change license policy title 2021-06-10 21:48:53 +00:00
server_test.go internal/frontend: add read-imports experiment to server tests 2021-06-10 13:37:38 +00:00
stdlib_test.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
styleguide.go content,internal: update pages to use new base layout 2021-06-07 15:09:35 +00:00
symbol.go internal: rename UnitSymbol to SymbolBuildContexts 2021-05-12 20:28:30 +00:00
symbol_test.go internal/frontend: support displaying multiple of same type 2021-05-06 17:43:23 +00:00
tabs.go content/static: refactor unit page to use new layout 2021-06-09 13:21:31 +00:00
unit.go content,internal: add description and title to unit page 2021-06-10 19:32:35 +00:00
unit_test.go internal: embed ModuleInfo into UnitMeta 2021-02-25 04:10:01 +00:00
urlinfo.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
urlinfo_test.go internal/version: drop Version suffix from constants 2021-06-07 16:08:09 +00:00
versions.go internal: enable symbol history for stdlib 2021-05-05 16:29:51 +00:00
versions_test.go internal/postgres: remove do-not-insert-new-documentation 2021-04-21 19:14:16 +00:00

readme.go

// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package frontend

import (
	"bytes"
	"context"

	"github.com/google/safehtml"
	"github.com/google/safehtml/template"
	"github.com/google/safehtml/uncheckedconversions"
	"github.com/microcosm-cc/bluemonday"
	"github.com/yuin/goldmark"
	emoji "github.com/yuin/goldmark-emoji"
	"github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/renderer"
	goldmarkHtml "github.com/yuin/goldmark/renderer/html"
	gmtext "github.com/yuin/goldmark/text"
	"github.com/yuin/goldmark/util"
	"golang.org/x/pkgsite/internal"
	"golang.org/x/pkgsite/internal/derrors"
	"golang.org/x/pkgsite/internal/source"
)

// Heading holds data about a heading and nested headings within a readme.
// This data is used in the sidebar template to render the readme outline.
type Heading struct {
	// Level is the original level of the heading.
	Level int
	// Text is the content from the readme contained within a heading.
	Text string
	// ID corresponds to the ID attribute for a heading element
	// and is also used in an href to the corresponding section
	// within the readme outline. All ids are prefixed with readme-
	// to avoid name collisions.
	ID string
	// Children are nested headings.
	Children []*Heading
	// parent is the heading this heading is nested within. Nil for top
	// level headings.
	parent *Heading
}

// Readme holds the result of processing a REAME file.
type Readme struct {
	HTML    safehtml.HTML // rendered HTML
	Outline []*Heading    // document headings
	Links   []link        // links from the "Links" section
}

// ProcessReadme processes the README of unit u, if it has one.
// Processing includes rendering and sanitizing the HTML or Markdown,
// and extracting headings and links.
//
// Headings are prefixed with "readme-" and heading levels are adjusted to start
// at h3 in order to nest them properly within the rest of the page. The
// readme's original styling is preserved in the html by giving headings a css
// class styled identical to their original heading level.
//
//  The extracted links are for display outside of the readme contents.
//
// This function is exported for use by external tools.
func ProcessReadme(ctx context.Context, u *internal.Unit) (_ *Readme, err error) {
	defer derrors.WrapAndReport(&err, "ProcessReadme(%q, %q, %q)", u.Path, u.ModulePath, u.Version)
	return processReadme(ctx, u.Readme, u.SourceInfo)
}

func processReadme(ctx context.Context, readme *internal.Readme, sourceInfo *source.Info) (_ *Readme, err error) {
	if readme == nil || readme.Contents == "" {
		return &Readme{}, nil
	}
	if !isMarkdown(readme.Filepath) {
		t := template.Must(template.New("").Parse(`<pre class="readme">{{.}}</pre>`))
		h, err := t.ExecuteToHTML(readme.Contents)
		if err != nil {
			return nil, err
		}
		return &Readme{HTML: h}, nil
	}

	// Sets priority value so that we always use our custom transformer
	// instead of the default ones. The default values are in:
	// https://github.com/yuin/goldmark/blob/7b90f04af43131db79ec320be0bd4744079b346f/parser/parser.go#L567
	const astTransformerPriority = 10000
	el := &extractLinks{ctx: ctx}
	et := &extractTOC{ctx: ctx, removeTitle: true}
	gdMarkdown := goldmark.New(
		goldmark.WithParserOptions(
			// WithHeadingAttribute allows us to include other attributes in
			// heading tags. This is useful for our aria-level implementation of
			// increasing heading rankings.
			parser.WithHeadingAttribute(),
			// Generates an id in every heading tag. This is used in github in
			// order to generate a link with a hash that a user would scroll to
			// <h1 id="goldmark">goldmark</h1> => github.com/yuin/goldmark#goldmark
			parser.WithAutoHeadingID(),
			// Include custom ASTTransformer using the readme and module info to
			// use translateRelativeLink and translateHTML to modify the AST
			// before it is rendered.
			parser.WithASTTransformers(
				util.Prioritized(&astTransformer{
					info:   sourceInfo,
					readme: readme,
				}, astTransformerPriority),
				// Extract links after we have transformed the URLs.
				util.Prioritized(el, astTransformerPriority+1),
				util.Prioritized(et, astTransformerPriority+1),
			),
		),
		// These extensions lets users write HTML code in the README. This is
		// fine since we process the contents using bluemonday after.
		goldmark.WithRendererOptions(goldmarkHtml.WithUnsafe(), goldmarkHtml.WithXHTML()),
		goldmark.WithExtensions(
			extension.GFM, // Support Github Flavored Markdown.
			emoji.Emoji,   // Support Github markdown emoji markup.
		),
	)
	gdMarkdown.Renderer().AddOptions(
		renderer.WithNodeRenderers(
			util.Prioritized(newHTMLRenderer(sourceInfo, readme), 100),
		),
	)
	contents := []byte(readme.Contents)
	gdParser := gdMarkdown.Parser()
	reader := gmtext.NewReader(contents)
	pctx := parser.NewContext(parser.WithIDs(newIDs()))
	doc := gdParser.Parse(reader, parser.WithContext(pctx))
	gdRenderer := gdMarkdown.Renderer()

	var b bytes.Buffer
	if err := gdRenderer.Render(&b, contents, doc); err != nil {
		return &Readme{}, nil
	}
	return &Readme{
		HTML:    sanitizeHTML(&b),
		Outline: et.Headings,
		Links:   el.links,
	}, nil
}

// sanitizeHTML sanitizes HTML from a bytes.Buffer so that it is safe.
func sanitizeHTML(b *bytes.Buffer) safehtml.HTML {
	p := bluemonday.UGCPolicy()

	p.AllowAttrs("width", "align").OnElements("img")
	p.AllowAttrs("width", "align").OnElements("div")
	p.AllowAttrs("width", "align").OnElements("p")
	// Allow accessible headings (i.e <div role="heading" aria-level="7">).
	p.AllowAttrs("width", "align", "role", "aria-level").OnElements("div")
	for _, h := range []string{"h1", "h2", "h3", "h4", "h5", "h6"} {
		// Needed to preserve github styles heading font-sizes
		p.AllowAttrs("class").OnElements(h)
	}

	s := string(p.SanitizeBytes(b.Bytes()))
	return uncheckedconversions.HTMLFromStringKnownToSatisfyTypeContract(s)
}