From 6e49f2148b116ee439c8a882dcfeefb6e7647c57 Mon Sep 17 00:00:00 2001 From: Tyler Bui-Palsulich <26876514+tbpg@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:16:02 -0400 Subject: [PATCH] feat(internal/godocfx): add prettyprint class to code blocks (#3819) Fixes #3804. --- .../godocfx/goldmark-codeblock/codeblock.go | 74 +++++++++++ internal/godocfx/parse.go | 3 +- internal/godocfx/testdata/golden/index.yml | 122 +++++++++--------- 3 files changed, 139 insertions(+), 60 deletions(-) create mode 100644 internal/godocfx/goldmark-codeblock/codeblock.go diff --git a/internal/godocfx/goldmark-codeblock/codeblock.go b/internal/godocfx/goldmark-codeblock/codeblock.go new file mode 100644 index 00000000000..9037bb6e5f0 --- /dev/null +++ b/internal/godocfx/goldmark-codeblock/codeblock.go @@ -0,0 +1,74 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldmarkcodeblock + +import ( + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/renderer" + "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/util" +) + +// codeBlockHTMLRenderer is a renderer.NodeRenderer implementation that +// renders CodeBlock nodes. +type codeBlockHTMLRenderer struct { + html.Config +} + +// newCodeBlockHTMLRenderer returns a new CodeblockHTMLRenderer. +func newCodeBlockHTMLRenderer(opts ...html.Option) renderer.NodeRenderer { + r := &codeBlockHTMLRenderer{ + Config: html.NewConfig(), + } + for _, opt := range opts { + opt.SetHTMLOption(&r.Config) + } + return r +} + +func (r *codeBlockHTMLRenderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + if entering { + _, _ = w.WriteString(`
`)
+
+ l := n.Lines().Len()
+ for i := 0; i < l; i++ {
+ line := n.Lines().At(i)
+ r.Writer.RawWrite(w, line.Value(source))
+ }
+ } else {
+ _, err := w.WriteString("
")
+ if err != nil {
+ return ast.WalkContinue, err
+ }
+ }
+ return ast.WalkContinue, nil
+}
+
+// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
+func (r *codeBlockHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(ast.KindFencedCodeBlock, r.renderCodeBlock)
+}
+
+type codeBlock struct{}
+
+// CodeBlock is an extenstion to add class="prettyprint" to code blocks.
+var CodeBlock = &codeBlock{}
+
+func (c *codeBlock) Extend(m goldmark.Markdown) {
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ util.Prioritized(newCodeBlockHTMLRenderer(), 1),
+ ))
+}
diff --git a/internal/godocfx/parse.go b/internal/godocfx/parse.go
index c5f4d4d4574..5b9f09f6e75 100644
--- a/internal/godocfx/parse.go
+++ b/internal/godocfx/parse.go
@@ -38,6 +38,7 @@ import (
"strconv"
"strings"
+ goldmarkcodeblock "cloud.google.com/go/internal/godocfx/goldmark-codeblock"
"cloud.google.com/go/third_party/go/doc"
"cloud.google.com/go/third_party/pkgsite"
"github.com/yuin/goldmark"
@@ -581,7 +582,7 @@ func toHTML(s string) string {
doc.ToMarkdown(buf, s, nil)
// Then, handle Markdown stuff, like lists and links.
- md := goldmark.New(goldmark.WithRendererOptions(html.WithUnsafe()))
+ md := goldmark.New(goldmark.WithRendererOptions(html.WithUnsafe()), goldmark.WithExtensions(goldmarkcodeblock.CodeBlock))
mdBuf := &bytes.Buffer{}
if err := md.Convert(buf.Bytes(), mdBuf); err != nil {
panic(err)
diff --git a/internal/godocfx/testdata/golden/index.yml b/internal/godocfx/testdata/golden/index.yml
index 6dc8c6cb3d6..8d127e93adf 100644
--- a/internal/godocfx/testdata/golden/index.yml
+++ b/internal/godocfx/testdata/golden/index.yml
@@ -12,82 +12,86 @@ items:
as described in\nhttps://cloud.google.com/storage/docs/exponential-backoff. Retrying
continues\nindefinitely unless the controlling context is canceled or the client
is closed. See\ncontext.WithTimeout and context.WithCancel.\nTo start working with this package, create a client:
\nctx
- := context.Background()\nclient, err := storage.NewClient(ctx)\nif err != nil
- {\n // TODO: Handle error.\n}\n
\nThe client will use your default - application credentials. Clients should be\nreused instead of created as needed. - The methods of Client are safe for\nconcurrent use by multiple goroutines.
\nIf + a Client\n
To start working with this package, create a client:
\nctx := context.Background()\nclient, err := storage.NewClient(ctx)\nif
+ err != nil {\n // TODO: Handle error.\n}\n
The client will use + your default application credentials. Clients should be\nreused instead of created + as needed. The methods of Client are safe for\nconcurrent use by multiple goroutines.
\nIf you only wish to access public data, you can create\nan unauthenticated client - with
\nclient, err := storage.NewClient(ctx, option.WithoutAuthentication())\n
\nA - Google Cloud Storage bucket is a collection of objects. To work with a\nbucket, - make a bucket handle:
\nbkt := client.Bucket(bucketName)\n
\nA + with
\nclient, err := storage.NewClient(ctx,
+ option.WithoutAuthentication())\n
A Google Cloud + Storage bucket is a collection of objects. To work with a\nbucket, make a bucket + handle:
\nbkt := client.Bucket(bucketName)\n
A handle is a reference to a bucket. You can have a handle even if the\nbucket doesn't - exist yet. To create a bucket in Google Cloud Storage,\ncall Create on the handle:
\nif
- err := bkt.Create(ctx, projectID, nil); err != nil {\n // TODO: Handle error.\n}\n
\nNote - that although buckets are associated with projects, bucket names are\nglobal across - all projects.
\nEach bucket has associated metadata, represented in this - package by\nBucketAttrs. The third argument to BucketHandle.Create allows you - to set\nthe initial BucketAttrs of a bucket. To retrieve a bucket's attributes, - use\nAttrs:
\nattrs, err := bkt.Attrs(ctx)\nif err != nil {\n //
- TODO: Handle error.\n}\nfmt.Printf("bucket %s, created at %s, is located
- in %s with storage class %s\\n",\n attrs.Name, attrs.Created, attrs.Location,
- attrs.StorageClass)\n
\nAn object holds arbitrary - data as a sequence of bytes, like a file. You\nrefer to objects using a handle, - just as with buckets, but unlike buckets\nyou don't explicitly create an object. - Instead, the first time you write\nto an object it will be created. You can use - the standard Go io.Reader\nand io.Writer interfaces to read and write object data:
\nobj
- := bkt.Object("data")\n// Write something to obj.\n// w implements io.Writer.\nw
- := obj.NewWriter(ctx)\n// Write some text to obj. This will either create the
- object or overwrite whatever is there already.\nif _, err := fmt.Fprintf(w, "This
- object contains text.\\n"); err != nil {\n // TODO: Handle error.\n}\n//
- Close, just like writing a file.\nif err := w.Close(); err != nil {\n // TODO:
- Handle error.\n}\n\n// Read it back.\nr, err := obj.NewReader(ctx)\nif err !=
- nil {\n // TODO: Handle error.\n}\ndefer r.Close()\nif _, err := io.Copy(os.Stdout,
- r); err != nil {\n // TODO: Handle error.\n}\n// Prints "This object contains
- text."\n
\nObjects also have attributes, which you can fetch - with Attrs:
\nobjAttrs, err := obj.Attrs(ctx)\nif err != nil {\n
- \ // TODO: Handle error.\n}\nfmt.Printf("object %s has size %d and can
- be read using %s\\n",\n objAttrs.Name, objAttrs.Size, objAttrs.MediaLink)\n
\nListing objects in a bucket is done with the Bucket.Objects method:
\nquery
+ exist yet. To create a bucket in Google Cloud Storage,\ncall Create on the handle:\nif err := bkt.Create(ctx, projectID, nil); err != nil {\n
+ \ // TODO: Handle error.\n}\n
Note that although buckets are
+ associated with projects, bucket names are\nglobal across all projects.
\nEach
+ bucket has associated metadata, represented in this package by\nBucketAttrs. The
+ third argument to BucketHandle.Create allows you to set\nthe initial BucketAttrs
+ of a bucket. To retrieve a bucket's attributes, use\nAttrs:
\nattrs,
+ err := bkt.Attrs(ctx)\nif err != nil {\n // TODO: Handle error.\n}\nfmt.Printf("bucket
+ %s, created at %s, is located in %s with storage class %s\\n",\n attrs.Name,
+ attrs.Created, attrs.Location, attrs.StorageClass)\n
Objects
\nAn
+ object holds arbitrary data as a sequence of bytes, like a file. You\nrefer to
+ objects using a handle, just as with buckets, but unlike buckets\nyou don't explicitly
+ create an object. Instead, the first time you write\nto an object it will be created.
+ You can use the standard Go io.Reader\nand io.Writer interfaces to read and write
+ object data:
\nobj := bkt.Object("data")\n//
+ Write something to obj.\n// w implements io.Writer.\nw := obj.NewWriter(ctx)\n//
+ Write some text to obj. This will either create the object or overwrite whatever
+ is there already.\nif _, err := fmt.Fprintf(w, "This object contains text.\\n");
+ err != nil {\n // TODO: Handle error.\n}\n// Close, just like writing a file.\nif
+ err := w.Close(); err != nil {\n // TODO: Handle error.\n}\n\n// Read it back.\nr,
+ err := obj.NewReader(ctx)\nif err != nil {\n // TODO: Handle error.\n}\ndefer
+ r.Close()\nif _, err := io.Copy(os.Stdout, r); err != nil {\n // TODO: Handle
+ error.\n}\n// Prints "This object contains text."\n
Objects
+ also have attributes, which you can fetch with Attrs:
\nobjAttrs,
+ err := obj.Attrs(ctx)\nif err != nil {\n // TODO: Handle error.\n}\nfmt.Printf("object
+ %s has size %d and can be read using %s\\n",\n objAttrs.Name, objAttrs.Size,
+ objAttrs.MediaLink)\n
Listing objects
\nListing objects
+ in a bucket is done with the Bucket.Objects method:
\nquery
:= &storage.Query{Prefix: ""}\n\nvar names []string\nit := bkt.Objects(ctx,
query)\nfor {\n attrs, err := it.Next()\n if err == iterator.Done {\n break\n
\ }\n if err != nil {\n log.Fatal(err)\n }\n names = append(names,
- attrs.Name)\n}\n
\nIf only a subset of object attributes is needed
+ attrs.Name)\n}\n
If only a subset of object attributes is needed when listing, specifying this\nsubset using Query.SetAttrSelection may speed up - the listing process:
\nquery := &storage.Query{Prefix: ""}\nquery.SetAttrSelection([]string{"Name"})\n\n//
- ... as before\n
\nBoth objects and buckets have - ACLs (Access Control Lists). An ACL is a list of\nACLRules, each of which specifies - the role of a user, group or project. ACLs\nare suitable for fine-grained control, - but you may prefer using IAM to control\naccess at the project level (see\nhttps://cloud.google.com/storage/docs/access-control/iam).
\nTo - list the ACLs of a bucket or object, obtain an ACLHandle and call its List method:
\nacls,
- err := obj.ACL().List(ctx)\nif err != nil {\n // TODO: Handle error.\n}\nfor
- _, rule := range acls {\n fmt.Printf("%s has role %s\\n", rule.Entity,
- rule.Role)\n}\n
\nYou can also set and delete ACLs.
\nEvery - object has a generation and a metageneration. The generation changes\nwhenever - the content changes, and the metageneration changes whenever the\nmetadata changes. - Conditions let you check these values before an operation;\nthe operation only - executes if the conditions match. You can use conditions to\nprevent race conditions - in read-modify-write operations.
\nFor example, say you've read an object's - metadata into objAttrs. Now\nyou want to write to that object, but only if its - contents haven't changed\nsince you read it. Here is how to express that:
\nw
+ the listing process:\nquery := &storage.Query{Prefix:
+ ""}\nquery.SetAttrSelection([]string{"Name"})\n\n// ... as
+ before\n
ACLs
\nBoth objects and buckets have ACLs (Access
+ Control Lists). An ACL is a list of\nACLRules, each of which specifies the role
+ of a user, group or project. ACLs\nare suitable for fine-grained control, but
+ you may prefer using IAM to control\naccess at the project level (see\nhttps://cloud.google.com/storage/docs/access-control/iam).
\nTo
+ list the ACLs of a bucket or object, obtain an ACLHandle and call its List method:
\nacls, err := obj.ACL().List(ctx)\nif err != nil {\n //
+ TODO: Handle error.\n}\nfor _, rule := range acls {\n fmt.Printf("%s has
+ role %s\\n", rule.Entity, rule.Role)\n}\n
You can also set
+ and delete ACLs.
\nConditions
\nEvery object has a generation and
+ a metageneration. The generation changes\nwhenever the content changes, and the
+ metageneration changes whenever the\nmetadata changes. Conditions let you check
+ these values before an operation;\nthe operation only executes if the conditions
+ match. You can use conditions to\nprevent race conditions in read-modify-write
+ operations.
\nFor example, say you've read an object's metadata into objAttrs.
+ Now\nyou want to write to that object, but only if its contents haven't changed\nsince
+ you read it. Here is how to express that:
\nw
= obj.If(storage.Conditions{GenerationMatch: objAttrs.Generation}).NewWriter(ctx)\n//
- Proceed with writing as above.\n
\nSigned URLs
\nYou can
+ Proceed with writing as above.\n
You can obtain a URL that lets anyone read or write an object for a limited time.\nYou don't need to create a client to do this. See the documentation of\nSignedURL - for details.
\nurl, err := storage.SignedURL(bucketName, "shared-object",
- opts)\nif err != nil {\n // TODO: Handle error.\n}\nfmt.Println(url)\n
\nurl, err := storage.SignedURL(bucketName,
+ "shared-object", opts)\nif err != nil {\n // TODO: Handle error.\n}\nfmt.Println(url)\n
A type of signed request that allows uploads through HTML forms directly to Cloud Storage with\ntemporary permission. Conditions can be applied to restrict how the HTML form is used and exercised\nby a user.
\nFor more information, please see https://cloud.google.com/storage/docs/xml-api/post-object - as well\nas the documentation of GenerateSignedPostPolicyV4.
\npv4,
+ as well\nas the documentation of GenerateSignedPostPolicyV4.\npv4,
err := storage.GenerateSignedPostPolicyV4(bucketName, objectName, opts)\nif err
!= nil {\n // TODO: Handle error.\n}\nfmt.Printf("URL: %s\\nFields; %v\\n",
- pv4.URL, pv4.Fields)\n
\nErrors
\nErrors returned by this
+ pv4.URL, pv4.Fields)\n
Errors returned by this
client are often of the type googleapi.Error
.\nThese
errors can be introspected for more information by type asserting to the richer
- googleapi.Error
type. For example:
if e, ok := err.(*googleapi.Error);
- ok {\n\t if e.Code == 409 { ... }\n}\n
\n"
+ googleapi.Error
type. For example:\nif
+ e, ok := err.(*googleapi.Error); ok {\n\t if e.Code == 409 { ... }\n}\n
"
type: package
langs:
- go