Skip to content
This repository was archived by the owner on Dec 16, 2025. It is now read-only.

Commit b2f019a

Browse files
committed
claat/render: basic markdown support
Not all nodes are implemented, yet.
1 parent 5cde6c0 commit b2f019a

4 files changed

Lines changed: 264 additions & 49 deletions

File tree

claat/render/md.go

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// Copyright 2016 Google Inc. All Rights Reserved.
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 render
16+
17+
import (
18+
"bytes"
19+
"io"
20+
"path"
21+
"sort"
22+
"strconv"
23+
"strings"
24+
25+
"github.com/googlecodelabs/tools/claat/types"
26+
)
27+
28+
// MD renders nodes as markdown for the target env.
29+
func MD(env string, nodes ...types.Node) (string, error) {
30+
var buf bytes.Buffer
31+
if err := WriteMD(&buf, env, nodes...); err != nil {
32+
return "", err
33+
}
34+
return buf.String(), nil
35+
}
36+
37+
// WriteMD does the same as MD but outputs rendered markup to w.
38+
func WriteMD(w io.Writer, env string, nodes ...types.Node) error {
39+
mw := mdWriter{w: w, env: env}
40+
return mw.write(nodes...)
41+
}
42+
43+
type mdWriter struct {
44+
w io.Writer // output writer
45+
env string // target environment
46+
err error // error during any writeXxx methods
47+
lineStart bool
48+
}
49+
50+
func (mw *mdWriter) writeBytes(b []byte) {
51+
if mw.err != nil {
52+
return
53+
}
54+
mw.lineStart = len(b) > 0 && b[len(b)-1] == '\n'
55+
_, mw.err = mw.w.Write(b)
56+
}
57+
58+
func (mw *mdWriter) writeString(s string) {
59+
mw.writeBytes([]byte(s))
60+
}
61+
62+
func (mw *mdWriter) space() {
63+
if !mw.lineStart {
64+
mw.writeString(" ")
65+
}
66+
}
67+
68+
func (mw *mdWriter) newBlock() {
69+
if !mw.lineStart {
70+
mw.writeBytes(newLine)
71+
}
72+
mw.writeBytes(newLine)
73+
}
74+
75+
func (mw *mdWriter) matchEnv(v []string) bool {
76+
if len(v) == 0 || mw.env == "" {
77+
return true
78+
}
79+
i := sort.SearchStrings(v, mw.env)
80+
return i < len(v) && v[i] == mw.env
81+
}
82+
83+
func (mw *mdWriter) write(nodes ...types.Node) error {
84+
for _, n := range nodes {
85+
if !mw.matchEnv(n.Env()) {
86+
continue
87+
}
88+
switch n := n.(type) {
89+
case *types.TextNode:
90+
mw.text(n)
91+
case *types.ImageNode:
92+
mw.image(n)
93+
case *types.URLNode:
94+
mw.url(n)
95+
case *types.ButtonNode:
96+
mw.write(n.Content.Nodes...)
97+
case *types.CodeNode:
98+
mw.code(n)
99+
case *types.ListNode:
100+
mw.list(n)
101+
case *types.ImportNode:
102+
if len(n.Content.Nodes) == 0 {
103+
break
104+
}
105+
mw.write(n.Content.Nodes...)
106+
case *types.ItemsListNode:
107+
mw.itemsList(n)
108+
//case *types.GridNode:
109+
// mw.grid(n)
110+
//case *types.InfoboxNode:
111+
// mw.infobox(n)
112+
//case *types.SurveyNode:
113+
// mw.survey(n)
114+
case *types.HeaderNode:
115+
mw.header(n)
116+
//case *types.YouTubeNode:
117+
// mw.youtube(n)
118+
}
119+
if mw.err != nil {
120+
return mw.err
121+
}
122+
}
123+
return nil
124+
}
125+
126+
func (mw *mdWriter) text(n *types.TextNode) {
127+
if n.Bold {
128+
mw.writeString("__")
129+
}
130+
if n.Italic {
131+
mw.writeString("*")
132+
}
133+
if n.Code {
134+
mw.writeString("`")
135+
}
136+
mw.writeString(n.Value)
137+
if n.Code {
138+
mw.writeString("`")
139+
}
140+
if n.Italic {
141+
mw.writeString("*")
142+
}
143+
if n.Bold {
144+
mw.writeString("__")
145+
}
146+
}
147+
148+
func (mw *mdWriter) image(n *types.ImageNode) {
149+
mw.space()
150+
mw.writeString("![")
151+
mw.writeString(path.Base(n.Src))
152+
mw.writeString("](")
153+
mw.writeString(n.Src)
154+
mw.writeString(")")
155+
}
156+
157+
func (mw *mdWriter) url(n *types.URLNode) {
158+
mw.space()
159+
if n.URL != "" {
160+
mw.writeString("[")
161+
}
162+
for _, cn := range n.Content.Nodes {
163+
if t, ok := cn.(*types.TextNode); ok {
164+
mw.writeString(t.Value)
165+
}
166+
}
167+
if n.URL != "" {
168+
mw.writeString("](")
169+
mw.writeString(n.URL)
170+
mw.writeString(")")
171+
}
172+
}
173+
174+
func (mw *mdWriter) code(n *types.CodeNode) {
175+
mw.newBlock()
176+
defer mw.writeBytes(newLine)
177+
if n.Term {
178+
var buf bytes.Buffer
179+
const prefix = " "
180+
lineStart := true
181+
for _, r := range n.Value {
182+
if lineStart {
183+
buf.WriteString(prefix)
184+
}
185+
buf.WriteRune(r)
186+
lineStart = r == '\n'
187+
}
188+
mw.writeBytes(buf.Bytes())
189+
return
190+
}
191+
mw.writeString("```")
192+
mw.writeString(n.Lang)
193+
mw.writeBytes(newLine)
194+
mw.writeString(n.Value)
195+
if !mw.lineStart {
196+
mw.writeBytes(newLine)
197+
}
198+
mw.writeString("```")
199+
}
200+
201+
func (mw *mdWriter) list(n *types.ListNode) {
202+
if n.Block() == true {
203+
mw.newBlock()
204+
}
205+
mw.write(n.Nodes...)
206+
if !mw.lineStart {
207+
mw.writeBytes(newLine)
208+
}
209+
}
210+
211+
func (mw *mdWriter) itemsList(n *types.ItemsListNode) {
212+
mw.newBlock()
213+
for i, item := range n.Items {
214+
s := "* "
215+
if n.Type() == types.NodeItemsList && n.Start > 0 {
216+
s = strconv.Itoa(i+n.Start) + ". "
217+
}
218+
mw.writeString(s)
219+
mw.write(item.Nodes...)
220+
if !mw.lineStart {
221+
mw.writeBytes(newLine)
222+
}
223+
}
224+
}
225+
226+
func (mw *mdWriter) header(n *types.HeaderNode) {
227+
mw.newBlock()
228+
mw.writeString(strings.Repeat("#", n.Level))
229+
mw.writeString(" ")
230+
mw.write(n.Content.Nodes...)
231+
if !mw.lineStart {
232+
mw.writeBytes(newLine)
233+
}
234+
}

claat/render/template.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type executer interface {
5454
// funcMap are exposted to the templates.
5555
var funcMap = map[string]interface{}{
5656
"renderHTML": HTML,
57+
"renderMD": MD,
5758
"matchEnv": func(tags []string, t string) bool {
5859
if len(tags) == 0 || t == "" {
5960
return true

claat/render/template.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
# {{.Meta.Title}}
22

3-
[//]: # (This is the default template for 'md' output format of the tool)
4-
[//]: # (This file is distributed under Apache-2 license; see LICENSE file at the root of this repo)
5-
63
{{if .Meta.Feedback}}[Codelab Feedback]({{.Meta.Feedback}}){{end}}
74

8-
{{range .Steps}}
5+
{{range .Steps}}{{if matchEnv .Tags $.Env}}
96
## {{.Title}}
107

11-
{{if .Duration}}Duration is {{.Duration}}{{end}}
12-
13-
{{range .Content.Nodes}}
14-
{{.}}
15-
{{end}}
16-
{{end}}
8+
{{if .Duration}}*Duration is {{.Duration.Minutes}} min*{{end}}
9+
{{.Content | renderMD $.Env}}
10+
{{end}}{{end}}

claat/render/tmpldata.go

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -328,45 +328,31 @@ var tmpldata = map[string]*template{
328328
html: false,
329329
bytes: []byte{
330330
0x23,0x20,0x7b,0x7b,0x2e,0x4d,0x65,0x74,0x61,0x2e,
331-
0x54,0x69,0x74,0x6c,0x65,0x7d,0x7d,0xa,0xa,0x5b,
332-
0x2f,0x2f,0x5d,0x3a,0x20,0x23,0x20,0x28,0x54,0x68,
333-
0x69,0x73,0x20,0x69,0x73,0x20,0x74,0x68,0x65,0x20,
334-
0x64,0x65,0x66,0x61,0x75,0x6c,0x74,0x20,0x74,0x65,
335-
0x6d,0x70,0x6c,0x61,0x74,0x65,0x20,0x66,0x6f,0x72,
336-
0x20,0x27,0x6d,0x64,0x27,0x20,0x6f,0x75,0x74,0x70,
337-
0x75,0x74,0x20,0x66,0x6f,0x72,0x6d,0x61,0x74,0x20,
338-
0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x74,0x6f,0x6f,
339-
0x6c,0x29,0xa,0x5b,0x2f,0x2f,0x5d,0x3a,0x20,0x23,
340-
0x20,0x28,0x54,0x68,0x69,0x73,0x20,0x66,0x69,0x6c,
341-
0x65,0x20,0x69,0x73,0x20,0x64,0x69,0x73,0x74,0x72,
342-
0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x75,0x6e,0x64,
343-
0x65,0x72,0x20,0x41,0x70,0x61,0x63,0x68,0x65,0x2d,
344-
0x32,0x20,0x6c,0x69,0x63,0x65,0x6e,0x73,0x65,0x3b,
345-
0x20,0x73,0x65,0x65,0x20,0x4c,0x49,0x43,0x45,0x4e,
346-
0x53,0x45,0x20,0x66,0x69,0x6c,0x65,0x20,0x61,0x74,
347-
0x20,0x74,0x68,0x65,0x20,0x72,0x6f,0x6f,0x74,0x20,
348-
0x6f,0x66,0x20,0x74,0x68,0x69,0x73,0x20,0x72,0x65,
349-
0x70,0x6f,0x29,0xa,0xa,0x7b,0x7b,0x69,0x66,0x20,
350-
0x2e,0x4d,0x65,0x74,0x61,0x2e,0x46,0x65,0x65,0x64,
351-
0x62,0x61,0x63,0x6b,0x7d,0x7d,0x5b,0x43,0x6f,0x64,
352-
0x65,0x6c,0x61,0x62,0x20,0x46,0x65,0x65,0x64,0x62,
353-
0x61,0x63,0x6b,0x5d,0x28,0x7b,0x7b,0x2e,0x4d,0x65,
354-
0x74,0x61,0x2e,0x46,0x65,0x65,0x64,0x62,0x61,0x63,
355-
0x6b,0x7d,0x7d,0x29,0x7b,0x7b,0x65,0x6e,0x64,0x7d,
356-
0x7d,0xa,0xa,0x7b,0x7b,0x72,0x61,0x6e,0x67,0x65,
357-
0x20,0x2e,0x53,0x74,0x65,0x70,0x73,0x7d,0x7d,0xa,
358-
0x23,0x23,0x20,0x7b,0x7b,0x2e,0x54,0x69,0x74,0x6c,
359-
0x65,0x7d,0x7d,0xa,0xa,0x7b,0x7b,0x69,0x66,0x20,
360-
0x2e,0x44,0x75,0x72,0x61,0x74,0x69,0x6f,0x6e,0x7d,
361-
0x7d,0x44,0x75,0x72,0x61,0x74,0x69,0x6f,0x6e,0x20,
362-
0x69,0x73,0x20,0x7b,0x7b,0x2e,0x44,0x75,0x72,0x61,
363-
0x74,0x69,0x6f,0x6e,0x7d,0x7d,0x7b,0x7b,0x65,0x6e,
364-
0x64,0x7d,0x7d,0xa,0xa,0x7b,0x7b,0x72,0x61,0x6e,
365-
0x67,0x65,0x20,0x2e,0x43,0x6f,0x6e,0x74,0x65,0x6e,
366-
0x74,0x2e,0x4e,0x6f,0x64,0x65,0x73,0x7d,0x7d,0xa,
367-
0x7b,0x7b,0x2e,0x7d,0x7d,0xa,0x7b,0x7b,0x65,0x6e,
368-
0x64,0x7d,0x7d,0xa,0x7b,0x7b,0x65,0x6e,0x64,0x7d,
369-
0x7d,0xa,
331+
0x54,0x69,0x74,0x6c,0x65,0x7d,0x7d,0xa,0xa,0x7b,
332+
0x7b,0x69,0x66,0x20,0x2e,0x4d,0x65,0x74,0x61,0x2e,
333+
0x46,0x65,0x65,0x64,0x62,0x61,0x63,0x6b,0x7d,0x7d,
334+
0x5b,0x43,0x6f,0x64,0x65,0x6c,0x61,0x62,0x20,0x46,
335+
0x65,0x65,0x64,0x62,0x61,0x63,0x6b,0x5d,0x28,0x7b,
336+
0x7b,0x2e,0x4d,0x65,0x74,0x61,0x2e,0x46,0x65,0x65,
337+
0x64,0x62,0x61,0x63,0x6b,0x7d,0x7d,0x29,0x7b,0x7b,
338+
0x65,0x6e,0x64,0x7d,0x7d,0xa,0xa,0x7b,0x7b,0x72,
339+
0x61,0x6e,0x67,0x65,0x20,0x2e,0x53,0x74,0x65,0x70,
340+
0x73,0x7d,0x7d,0x7b,0x7b,0x69,0x66,0x20,0x6d,0x61,
341+
0x74,0x63,0x68,0x45,0x6e,0x76,0x20,0x2e,0x54,0x61,
342+
0x67,0x73,0x20,0x24,0x2e,0x45,0x6e,0x76,0x7d,0x7d,
343+
0xa,0x23,0x23,0x20,0x7b,0x7b,0x2e,0x54,0x69,0x74,
344+
0x6c,0x65,0x7d,0x7d,0xa,0xa,0x7b,0x7b,0x69,0x66,
345+
0x20,0x2e,0x44,0x75,0x72,0x61,0x74,0x69,0x6f,0x6e,
346+
0x7d,0x7d,0x2a,0x44,0x75,0x72,0x61,0x74,0x69,0x6f,
347+
0x6e,0x20,0x69,0x73,0x20,0x7b,0x7b,0x2e,0x44,0x75,
348+
0x72,0x61,0x74,0x69,0x6f,0x6e,0x2e,0x4d,0x69,0x6e,
349+
0x75,0x74,0x65,0x73,0x7d,0x7d,0x20,0x6d,0x69,0x6e,
350+
0x2a,0x7b,0x7b,0x65,0x6e,0x64,0x7d,0x7d,0xa,0x7b,
351+
0x7b,0x2e,0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x20,
352+
0x7c,0x20,0x72,0x65,0x6e,0x64,0x65,0x72,0x4d,0x44,
353+
0x20,0x24,0x2e,0x45,0x6e,0x76,0x7d,0x7d,0xa,0x7b,
354+
0x7b,0x65,0x6e,0x64,0x7d,0x7d,0x7b,0x7b,0x65,0x6e,
355+
0x64,0x7d,0x7d,0xa,
370356
},
371357
},
372358
}

0 commit comments

Comments
 (0)