Skip to content

Commit 6e49f21

Browse files
authored
feat(internal/godocfx): add prettyprint class to code blocks (#3819)
Fixes #3804.
1 parent df28999 commit 6e49f21

File tree

3 files changed

+139
-60
lines changed

3 files changed

+139
-60
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package goldmarkcodeblock
16+
17+
import (
18+
"github.com/yuin/goldmark"
19+
"github.com/yuin/goldmark/ast"
20+
"github.com/yuin/goldmark/renderer"
21+
"github.com/yuin/goldmark/renderer/html"
22+
"github.com/yuin/goldmark/util"
23+
)
24+
25+
// codeBlockHTMLRenderer is a renderer.NodeRenderer implementation that
26+
// renders CodeBlock nodes.
27+
type codeBlockHTMLRenderer struct {
28+
html.Config
29+
}
30+
31+
// newCodeBlockHTMLRenderer returns a new CodeblockHTMLRenderer.
32+
func newCodeBlockHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
33+
r := &codeBlockHTMLRenderer{
34+
Config: html.NewConfig(),
35+
}
36+
for _, opt := range opts {
37+
opt.SetHTMLOption(&r.Config)
38+
}
39+
return r
40+
}
41+
42+
func (r *codeBlockHTMLRenderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
43+
if entering {
44+
_, _ = w.WriteString(`<pre><code class="prettyprint">`)
45+
46+
l := n.Lines().Len()
47+
for i := 0; i < l; i++ {
48+
line := n.Lines().At(i)
49+
r.Writer.RawWrite(w, line.Value(source))
50+
}
51+
} else {
52+
_, err := w.WriteString("</code></pre>")
53+
if err != nil {
54+
return ast.WalkContinue, err
55+
}
56+
}
57+
return ast.WalkContinue, nil
58+
}
59+
60+
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
61+
func (r *codeBlockHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
62+
reg.Register(ast.KindFencedCodeBlock, r.renderCodeBlock)
63+
}
64+
65+
type codeBlock struct{}
66+
67+
// CodeBlock is an extenstion to add class="prettyprint" to code blocks.
68+
var CodeBlock = &codeBlock{}
69+
70+
func (c *codeBlock) Extend(m goldmark.Markdown) {
71+
m.Renderer().AddOptions(renderer.WithNodeRenderers(
72+
util.Prioritized(newCodeBlockHTMLRenderer(), 1),
73+
))
74+
}

internal/godocfx/parse.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"strconv"
3939
"strings"
4040

41+
goldmarkcodeblock "cloud.google.com/go/internal/godocfx/goldmark-codeblock"
4142
"cloud.google.com/go/third_party/go/doc"
4243
"cloud.google.com/go/third_party/pkgsite"
4344
"github.com/yuin/goldmark"
@@ -581,7 +582,7 @@ func toHTML(s string) string {
581582
doc.ToMarkdown(buf, s, nil)
582583

583584
// Then, handle Markdown stuff, like lists and links.
584-
md := goldmark.New(goldmark.WithRendererOptions(html.WithUnsafe()))
585+
md := goldmark.New(goldmark.WithRendererOptions(html.WithUnsafe()), goldmark.WithExtensions(goldmarkcodeblock.CodeBlock))
585586
mdBuf := &bytes.Buffer{}
586587
if err := md.Convert(buf.Bytes(), mdBuf); err != nil {
587588
panic(err)

internal/godocfx/testdata/golden/index.yml

Lines changed: 63 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -12,82 +12,86 @@ items:
1212
as described in\nhttps://cloud.google.com/storage/docs/exponential-backoff. Retrying
1313
continues\nindefinitely unless the controlling context is canceled or the client
1414
is closed. See\ncontext.WithTimeout and context.WithCancel.</p>\n<h3>Creating
15-
a Client</h3>\n<p>To start working with this package, create a client:</p>\n<pre><code>ctx
16-
:= context.Background()\nclient, err := storage.NewClient(ctx)\nif err != nil
17-
{\n // TODO: Handle error.\n}\n</code></pre>\n<p>The client will use your default
18-
application credentials. Clients should be\nreused instead of created as needed.
19-
The methods of Client are safe for\nconcurrent use by multiple goroutines.</p>\n<p>If
15+
a Client</h3>\n<p>To start working with this package, create a client:</p>\n<pre><code
16+
class=\"prettyprint\">ctx := context.Background()\nclient, err := storage.NewClient(ctx)\nif
17+
err != nil {\n // TODO: Handle error.\n}\n</code></pre><p>The client will use
18+
your default application credentials. Clients should be\nreused instead of created
19+
as needed. The methods of Client are safe for\nconcurrent use by multiple goroutines.</p>\n<p>If
2020
you only wish to access public data, you can create\nan unauthenticated client
21-
with</p>\n<pre><code>client, err := storage.NewClient(ctx, option.WithoutAuthentication())\n</code></pre>\n<h3>Buckets</h3>\n<p>A
22-
Google Cloud Storage bucket is a collection of objects. To work with a\nbucket,
23-
make a bucket handle:</p>\n<pre><code>bkt := client.Bucket(bucketName)\n</code></pre>\n<p>A
21+
with</p>\n<pre><code class=\"prettyprint\">client, err := storage.NewClient(ctx,
22+
option.WithoutAuthentication())\n</code></pre><h3>Buckets</h3>\n<p>A Google Cloud
23+
Storage bucket is a collection of objects. To work with a\nbucket, make a bucket
24+
handle:</p>\n<pre><code class=\"prettyprint\">bkt := client.Bucket(bucketName)\n</code></pre><p>A
2425
handle is a reference to a bucket. You can have a handle even if the\nbucket doesn't
25-
exist yet. To create a bucket in Google Cloud Storage,\ncall Create on the handle:</p>\n<pre><code>if
26-
err := bkt.Create(ctx, projectID, nil); err != nil {\n // TODO: Handle error.\n}\n</code></pre>\n<p>Note
27-
that although buckets are associated with projects, bucket names are\nglobal across
28-
all projects.</p>\n<p>Each bucket has associated metadata, represented in this
29-
package by\nBucketAttrs. The third argument to BucketHandle.Create allows you
30-
to set\nthe initial BucketAttrs of a bucket. To retrieve a bucket's attributes,
31-
use\nAttrs:</p>\n<pre><code>attrs, err := bkt.Attrs(ctx)\nif err != nil {\n //
32-
TODO: Handle error.\n}\nfmt.Printf(&quot;bucket %s, created at %s, is located
33-
in %s with storage class %s\\n&quot;,\n attrs.Name, attrs.Created, attrs.Location,
34-
attrs.StorageClass)\n</code></pre>\n<h3>Objects</h3>\n<p>An object holds arbitrary
35-
data as a sequence of bytes, like a file. You\nrefer to objects using a handle,
36-
just as with buckets, but unlike buckets\nyou don't explicitly create an object.
37-
Instead, the first time you write\nto an object it will be created. You can use
38-
the standard Go io.Reader\nand io.Writer interfaces to read and write object data:</p>\n<pre><code>obj
39-
:= bkt.Object(&quot;data&quot;)\n// Write something to obj.\n// w implements io.Writer.\nw
40-
:= obj.NewWriter(ctx)\n// Write some text to obj. This will either create the
41-
object or overwrite whatever is there already.\nif _, err := fmt.Fprintf(w, &quot;This
42-
object contains text.\\n&quot;); err != nil {\n // TODO: Handle error.\n}\n//
43-
Close, just like writing a file.\nif err := w.Close(); err != nil {\n // TODO:
44-
Handle error.\n}\n\n// Read it back.\nr, err := obj.NewReader(ctx)\nif err !=
45-
nil {\n // TODO: Handle error.\n}\ndefer r.Close()\nif _, err := io.Copy(os.Stdout,
46-
r); err != nil {\n // TODO: Handle error.\n}\n// Prints &quot;This object contains
47-
text.&quot;\n</code></pre>\n<p>Objects also have attributes, which you can fetch
48-
with Attrs:</p>\n<pre><code>objAttrs, err := obj.Attrs(ctx)\nif err != nil {\n
49-
\ // TODO: Handle error.\n}\nfmt.Printf(&quot;object %s has size %d and can
50-
be read using %s\\n&quot;,\n objAttrs.Name, objAttrs.Size, objAttrs.MediaLink)\n</code></pre>\n<h3>Listing
51-
objects</h3>\n<p>Listing objects in a bucket is done with the Bucket.Objects method:</p>\n<pre><code>query
26+
exist yet. To create a bucket in Google Cloud Storage,\ncall Create on the handle:</p>\n<pre><code
27+
class=\"prettyprint\">if err := bkt.Create(ctx, projectID, nil); err != nil {\n
28+
\ // TODO: Handle error.\n}\n</code></pre><p>Note that although buckets are
29+
associated with projects, bucket names are\nglobal across all projects.</p>\n<p>Each
30+
bucket has associated metadata, represented in this package by\nBucketAttrs. The
31+
third argument to BucketHandle.Create allows you to set\nthe initial BucketAttrs
32+
of a bucket. To retrieve a bucket's attributes, use\nAttrs:</p>\n<pre><code class=\"prettyprint\">attrs,
33+
err := bkt.Attrs(ctx)\nif err != nil {\n // TODO: Handle error.\n}\nfmt.Printf(&quot;bucket
34+
%s, created at %s, is located in %s with storage class %s\\n&quot;,\n attrs.Name,
35+
attrs.Created, attrs.Location, attrs.StorageClass)\n</code></pre><h3>Objects</h3>\n<p>An
36+
object holds arbitrary data as a sequence of bytes, like a file. You\nrefer to
37+
objects using a handle, just as with buckets, but unlike buckets\nyou don't explicitly
38+
create an object. Instead, the first time you write\nto an object it will be created.
39+
You can use the standard Go io.Reader\nand io.Writer interfaces to read and write
40+
object data:</p>\n<pre><code class=\"prettyprint\">obj := bkt.Object(&quot;data&quot;)\n//
41+
Write something to obj.\n// w implements io.Writer.\nw := obj.NewWriter(ctx)\n//
42+
Write some text to obj. This will either create the object or overwrite whatever
43+
is there already.\nif _, err := fmt.Fprintf(w, &quot;This object contains text.\\n&quot;);
44+
err != nil {\n // TODO: Handle error.\n}\n// Close, just like writing a file.\nif
45+
err := w.Close(); err != nil {\n // TODO: Handle error.\n}\n\n// Read it back.\nr,
46+
err := obj.NewReader(ctx)\nif err != nil {\n // TODO: Handle error.\n}\ndefer
47+
r.Close()\nif _, err := io.Copy(os.Stdout, r); err != nil {\n // TODO: Handle
48+
error.\n}\n// Prints &quot;This object contains text.&quot;\n</code></pre><p>Objects
49+
also have attributes, which you can fetch with Attrs:</p>\n<pre><code class=\"prettyprint\">objAttrs,
50+
err := obj.Attrs(ctx)\nif err != nil {\n // TODO: Handle error.\n}\nfmt.Printf(&quot;object
51+
%s has size %d and can be read using %s\\n&quot;,\n objAttrs.Name, objAttrs.Size,
52+
objAttrs.MediaLink)\n</code></pre><h3>Listing objects</h3>\n<p>Listing objects
53+
in a bucket is done with the Bucket.Objects method:</p>\n<pre><code class=\"prettyprint\">query
5254
:= &amp;storage.Query{Prefix: &quot;&quot;}\n\nvar names []string\nit := bkt.Objects(ctx,
5355
query)\nfor {\n attrs, err := it.Next()\n if err == iterator.Done {\n break\n
5456
\ }\n if err != nil {\n log.Fatal(err)\n }\n names = append(names,
55-
attrs.Name)\n}\n</code></pre>\n<p>If only a subset of object attributes is needed
57+
attrs.Name)\n}\n</code></pre><p>If only a subset of object attributes is needed
5658
when listing, specifying this\nsubset using Query.SetAttrSelection may speed up
57-
the listing process:</p>\n<pre><code>query := &amp;storage.Query{Prefix: &quot;&quot;}\nquery.SetAttrSelection([]string{&quot;Name&quot;})\n\n//
58-
... as before\n</code></pre>\n<h3>ACLs</h3>\n<p>Both objects and buckets have
59-
ACLs (Access Control Lists). An ACL is a list of\nACLRules, each of which specifies
60-
the role of a user, group or project. ACLs\nare suitable for fine-grained control,
61-
but you may prefer using IAM to control\naccess at the project level (see\nhttps://cloud.google.com/storage/docs/access-control/iam).</p>\n<p>To
62-
list the ACLs of a bucket or object, obtain an ACLHandle and call its List method:</p>\n<pre><code>acls,
63-
err := obj.ACL().List(ctx)\nif err != nil {\n // TODO: Handle error.\n}\nfor
64-
_, rule := range acls {\n fmt.Printf(&quot;%s has role %s\\n&quot;, rule.Entity,
65-
rule.Role)\n}\n</code></pre>\n<p>You can also set and delete ACLs.</p>\n<h3>Conditions</h3>\n<p>Every
66-
object has a generation and a metageneration. The generation changes\nwhenever
67-
the content changes, and the metageneration changes whenever the\nmetadata changes.
68-
Conditions let you check these values before an operation;\nthe operation only
69-
executes if the conditions match. You can use conditions to\nprevent race conditions
70-
in read-modify-write operations.</p>\n<p>For example, say you've read an object's
71-
metadata into objAttrs. Now\nyou want to write to that object, but only if its
72-
contents haven't changed\nsince you read it. Here is how to express that:</p>\n<pre><code>w
59+
the listing process:</p>\n<pre><code class=\"prettyprint\">query := &amp;storage.Query{Prefix:
60+
&quot;&quot;}\nquery.SetAttrSelection([]string{&quot;Name&quot;})\n\n// ... as
61+
before\n</code></pre><h3>ACLs</h3>\n<p>Both objects and buckets have ACLs (Access
62+
Control Lists). An ACL is a list of\nACLRules, each of which specifies the role
63+
of a user, group or project. ACLs\nare suitable for fine-grained control, but
64+
you may prefer using IAM to control\naccess at the project level (see\nhttps://cloud.google.com/storage/docs/access-control/iam).</p>\n<p>To
65+
list the ACLs of a bucket or object, obtain an ACLHandle and call its List method:</p>\n<pre><code
66+
class=\"prettyprint\">acls, err := obj.ACL().List(ctx)\nif err != nil {\n //
67+
TODO: Handle error.\n}\nfor _, rule := range acls {\n fmt.Printf(&quot;%s has
68+
role %s\\n&quot;, rule.Entity, rule.Role)\n}\n</code></pre><p>You can also set
69+
and delete ACLs.</p>\n<h3>Conditions</h3>\n<p>Every object has a generation and
70+
a metageneration. The generation changes\nwhenever the content changes, and the
71+
metageneration changes whenever the\nmetadata changes. Conditions let you check
72+
these values before an operation;\nthe operation only executes if the conditions
73+
match. You can use conditions to\nprevent race conditions in read-modify-write
74+
operations.</p>\n<p>For example, say you've read an object's metadata into objAttrs.
75+
Now\nyou want to write to that object, but only if its contents haven't changed\nsince
76+
you read it. Here is how to express that:</p>\n<pre><code class=\"prettyprint\">w
7377
= obj.If(storage.Conditions{GenerationMatch: objAttrs.Generation}).NewWriter(ctx)\n//
74-
Proceed with writing as above.\n</code></pre>\n<h3>Signed URLs</h3>\n<p>You can
78+
Proceed with writing as above.\n</code></pre><h3>Signed URLs</h3>\n<p>You can
7579
obtain a URL that lets anyone read or write an object for a limited time.\nYou
7680
don't need to create a client to do this. See the documentation of\nSignedURL
77-
for details.</p>\n<pre><code>url, err := storage.SignedURL(bucketName, &quot;shared-object&quot;,
78-
opts)\nif err != nil {\n // TODO: Handle error.\n}\nfmt.Println(url)\n</code></pre>\n<h3>Post
81+
for details.</p>\n<pre><code class=\"prettyprint\">url, err := storage.SignedURL(bucketName,
82+
&quot;shared-object&quot;, opts)\nif err != nil {\n // TODO: Handle error.\n}\nfmt.Println(url)\n</code></pre><h3>Post
7983
Policy V4 Signed Request</h3>\n<p>A type of signed request that allows uploads
8084
through HTML forms directly to Cloud Storage with\ntemporary permission. Conditions
8185
can be applied to restrict how the HTML form is used and exercised\nby a user.</p>\n<p>For
8286
more information, please see https://cloud.google.com/storage/docs/xml-api/post-object
83-
as well\nas the documentation of GenerateSignedPostPolicyV4.</p>\n<pre><code>pv4,
87+
as well\nas the documentation of GenerateSignedPostPolicyV4.</p>\n<pre><code class=\"prettyprint\">pv4,
8488
err := storage.GenerateSignedPostPolicyV4(bucketName, objectName, opts)\nif err
8589
!= nil {\n // TODO: Handle error.\n}\nfmt.Printf(&quot;URL: %s\\nFields; %v\\n&quot;,
86-
pv4.URL, pv4.Fields)\n</code></pre>\n<h3>Errors</h3>\n<p>Errors returned by this
90+
pv4.URL, pv4.Fields)\n</code></pre><h3>Errors</h3>\n<p>Errors returned by this
8791
client are often of the type <a href=\"https://godoc.org/google.golang.org/api/googleapi#Error\"><code>googleapi.Error</code></a>.\nThese
8892
errors can be introspected for more information by type asserting to the richer
89-
<code>googleapi.Error</code> type. For example:</p>\n<pre><code>if e, ok := err.(*googleapi.Error);
90-
ok {\n\t if e.Code == 409 { ... }\n}\n</code></pre>\n"
93+
<code>googleapi.Error</code> type. For example:</p>\n<pre><code class=\"prettyprint\">if
94+
e, ok := err.(*googleapi.Error); ok {\n\t if e.Code == 409 { ... }\n}\n</code></pre>"
9195
type: package
9296
langs:
9397
- go

0 commit comments

Comments
 (0)