convert_test.go (12509B)
1 // Copyright 2020 The Hugo Authors. 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 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 // Package asciidocext converts AsciiDoc to HTML using Asciidoctor
15 // external binary. The `asciidoc` module is reserved for a future golang
16 // implementation.
17
18 package asciidocext
19
20 import (
21 "path/filepath"
22 "testing"
23
24 "github.com/gohugoio/hugo/common/hexec"
25 "github.com/gohugoio/hugo/common/loggers"
26 "github.com/gohugoio/hugo/config"
27 "github.com/gohugoio/hugo/config/security"
28 "github.com/gohugoio/hugo/markup/converter"
29 "github.com/gohugoio/hugo/markup/markup_config"
30 "github.com/gohugoio/hugo/markup/tableofcontents"
31
32 qt "github.com/frankban/quicktest"
33 )
34
35 func TestAsciidoctorDefaultArgs(t *testing.T) {
36 c := qt.New(t)
37 cfg := config.New()
38 mconf := markup_config.Default
39
40 p, err := Provider.New(
41 converter.ProviderConfig{
42 Cfg: cfg,
43 MarkupConfig: mconf,
44 Logger: loggers.NewErrorLogger(),
45 },
46 )
47 c.Assert(err, qt.IsNil)
48
49 conv, err := p.New(converter.DocumentContext{})
50 c.Assert(err, qt.IsNil)
51
52 ac := conv.(*asciidocConverter)
53 c.Assert(ac, qt.Not(qt.IsNil))
54
55 args := ac.parseArgs(converter.DocumentContext{})
56 expected := []string{"--no-header-footer"}
57 c.Assert(args, qt.DeepEquals, expected)
58 }
59
60 func TestAsciidoctorNonDefaultArgs(t *testing.T) {
61 c := qt.New(t)
62 cfg := config.New()
63 mconf := markup_config.Default
64 mconf.AsciidocExt.Backend = "manpage"
65 mconf.AsciidocExt.NoHeaderOrFooter = false
66 mconf.AsciidocExt.SafeMode = "safe"
67 mconf.AsciidocExt.SectionNumbers = true
68 mconf.AsciidocExt.Verbose = true
69 mconf.AsciidocExt.Trace = false
70 mconf.AsciidocExt.FailureLevel = "warn"
71 p, err := Provider.New(
72 converter.ProviderConfig{
73 Cfg: cfg,
74 MarkupConfig: mconf,
75 Logger: loggers.NewErrorLogger(),
76 },
77 )
78 c.Assert(err, qt.IsNil)
79
80 conv, err := p.New(converter.DocumentContext{})
81 c.Assert(err, qt.IsNil)
82
83 ac := conv.(*asciidocConverter)
84 c.Assert(ac, qt.Not(qt.IsNil))
85
86 args := ac.parseArgs(converter.DocumentContext{})
87 expected := []string{"-b", "manpage", "--section-numbers", "--verbose", "--failure-level", "warn", "--safe-mode", "safe"}
88 c.Assert(args, qt.DeepEquals, expected)
89 }
90
91 func TestAsciidoctorDisallowedArgs(t *testing.T) {
92 c := qt.New(t)
93 cfg := config.New()
94 mconf := markup_config.Default
95 mconf.AsciidocExt.Backend = "disallowed-backend"
96 mconf.AsciidocExt.Extensions = []string{"./disallowed-extension"}
97 mconf.AsciidocExt.Attributes = map[string]string{"outdir": "disallowed-attribute"}
98 mconf.AsciidocExt.SafeMode = "disallowed-safemode"
99 mconf.AsciidocExt.FailureLevel = "disallowed-failurelevel"
100 p, err := Provider.New(
101 converter.ProviderConfig{
102 Cfg: cfg,
103 MarkupConfig: mconf,
104 Logger: loggers.NewErrorLogger(),
105 },
106 )
107 c.Assert(err, qt.IsNil)
108
109 conv, err := p.New(converter.DocumentContext{})
110 c.Assert(err, qt.IsNil)
111
112 ac := conv.(*asciidocConverter)
113 c.Assert(ac, qt.Not(qt.IsNil))
114
115 args := ac.parseArgs(converter.DocumentContext{})
116 expected := []string{"--no-header-footer"}
117 c.Assert(args, qt.DeepEquals, expected)
118 }
119
120 func TestAsciidoctorArbitraryExtension(t *testing.T) {
121 c := qt.New(t)
122 cfg := config.New()
123 mconf := markup_config.Default
124 mconf.AsciidocExt.Extensions = []string{"arbitrary-extension"}
125 p, err := Provider.New(
126 converter.ProviderConfig{
127 Cfg: cfg,
128 MarkupConfig: mconf,
129 Logger: loggers.NewErrorLogger(),
130 },
131 )
132 c.Assert(err, qt.IsNil)
133
134 conv, err := p.New(converter.DocumentContext{})
135 c.Assert(err, qt.IsNil)
136
137 ac := conv.(*asciidocConverter)
138 c.Assert(ac, qt.Not(qt.IsNil))
139
140 args := ac.parseArgs(converter.DocumentContext{})
141 expected := []string{"-r", "arbitrary-extension", "--no-header-footer"}
142 c.Assert(args, qt.DeepEquals, expected)
143 }
144
145 func TestAsciidoctorDisallowedExtension(t *testing.T) {
146 c := qt.New(t)
147 cfg := config.New()
148 for _, disallowedExtension := range []string{
149 `foo-bar//`,
150 `foo-bar\\ `,
151 `../../foo-bar`,
152 `/foo-bar`,
153 `C:\foo-bar`,
154 `foo-bar.rb`,
155 `foo.bar`,
156 } {
157 mconf := markup_config.Default
158 mconf.AsciidocExt.Extensions = []string{disallowedExtension}
159 p, err := Provider.New(
160 converter.ProviderConfig{
161 Cfg: cfg,
162 MarkupConfig: mconf,
163 Logger: loggers.NewErrorLogger(),
164 },
165 )
166 c.Assert(err, qt.IsNil)
167
168 conv, err := p.New(converter.DocumentContext{})
169 c.Assert(err, qt.IsNil)
170
171 ac := conv.(*asciidocConverter)
172 c.Assert(ac, qt.Not(qt.IsNil))
173
174 args := ac.parseArgs(converter.DocumentContext{})
175 expected := []string{"--no-header-footer"}
176 c.Assert(args, qt.DeepEquals, expected)
177 }
178 }
179
180 func TestAsciidoctorWorkingFolderCurrent(t *testing.T) {
181 c := qt.New(t)
182 cfg := config.New()
183 mconf := markup_config.Default
184 mconf.AsciidocExt.WorkingFolderCurrent = true
185 mconf.AsciidocExt.Trace = false
186 p, err := Provider.New(
187 converter.ProviderConfig{
188 Cfg: cfg,
189 MarkupConfig: mconf,
190 Logger: loggers.NewErrorLogger(),
191 },
192 )
193 c.Assert(err, qt.IsNil)
194
195 ctx := converter.DocumentContext{Filename: "/tmp/hugo_asciidoc_ddd/docs/chapter2/index.adoc", DocumentName: "chapter2/index.adoc"}
196 conv, err := p.New(ctx)
197 c.Assert(err, qt.IsNil)
198
199 ac := conv.(*asciidocConverter)
200 c.Assert(ac, qt.Not(qt.IsNil))
201
202 args := ac.parseArgs(ctx)
203 c.Assert(len(args), qt.Equals, 5)
204 c.Assert(args[0], qt.Equals, "--base-dir")
205 c.Assert(filepath.ToSlash(args[1]), qt.Matches, "/tmp/hugo_asciidoc_ddd/docs/chapter2")
206 c.Assert(args[2], qt.Equals, "-a")
207 c.Assert(args[3], qt.Matches, `outdir=.*[/\\]{1,2}asciidocext[/\\]{1,2}chapter2`)
208 c.Assert(args[4], qt.Equals, "--no-header-footer")
209 }
210
211 func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
212 c := qt.New(t)
213 cfg := config.New()
214 mconf := markup_config.Default
215 mconf.AsciidocExt.NoHeaderOrFooter = true
216 mconf.AsciidocExt.Extensions = []string{"asciidoctor-html5s", "asciidoctor-diagram"}
217 mconf.AsciidocExt.Backend = "html5s"
218 mconf.AsciidocExt.WorkingFolderCurrent = true
219 mconf.AsciidocExt.Trace = false
220 p, err := Provider.New(
221 converter.ProviderConfig{
222 Cfg: cfg,
223 MarkupConfig: mconf,
224 Logger: loggers.NewErrorLogger(),
225 },
226 )
227 c.Assert(err, qt.IsNil)
228
229 conv, err := p.New(converter.DocumentContext{})
230 c.Assert(err, qt.IsNil)
231
232 ac := conv.(*asciidocConverter)
233 c.Assert(ac, qt.Not(qt.IsNil))
234
235 args := ac.parseArgs(converter.DocumentContext{})
236 c.Assert(len(args), qt.Equals, 11)
237 c.Assert(args[0], qt.Equals, "-b")
238 c.Assert(args[1], qt.Equals, "html5s")
239 c.Assert(args[2], qt.Equals, "-r")
240 c.Assert(args[3], qt.Equals, "asciidoctor-html5s")
241 c.Assert(args[4], qt.Equals, "-r")
242 c.Assert(args[5], qt.Equals, "asciidoctor-diagram")
243 c.Assert(args[6], qt.Equals, "--base-dir")
244 c.Assert(args[7], qt.Equals, ".")
245 c.Assert(args[8], qt.Equals, "-a")
246 c.Assert(args[9], qt.Contains, "outdir=")
247 c.Assert(args[10], qt.Equals, "--no-header-footer")
248 }
249
250 func TestAsciidoctorAttributes(t *testing.T) {
251 c := qt.New(t)
252 cfg := config.New()
253 mconf := markup_config.Default
254 mconf.AsciidocExt.Attributes = map[string]string{"my-base-url": "https://gohugo.io/", "my-attribute-name": "my value"}
255 mconf.AsciidocExt.Trace = false
256 p, err := Provider.New(
257 converter.ProviderConfig{
258 Cfg: cfg,
259 MarkupConfig: mconf,
260 Logger: loggers.NewErrorLogger(),
261 },
262 )
263 c.Assert(err, qt.IsNil)
264
265 conv, err := p.New(converter.DocumentContext{})
266 c.Assert(err, qt.IsNil)
267
268 ac := conv.(*asciidocConverter)
269 c.Assert(ac, qt.Not(qt.IsNil))
270
271 expectedValues := map[string]bool{
272 "my-base-url=https://gohugo.io/": true,
273 "my-attribute-name=my value": true,
274 }
275
276 args := ac.parseArgs(converter.DocumentContext{})
277 c.Assert(len(args), qt.Equals, 5)
278 c.Assert(args[0], qt.Equals, "-a")
279 c.Assert(expectedValues[args[1]], qt.Equals, true)
280 c.Assert(args[2], qt.Equals, "-a")
281 c.Assert(expectedValues[args[3]], qt.Equals, true)
282 c.Assert(args[4], qt.Equals, "--no-header-footer")
283 }
284
285 func getProvider(c *qt.C, mconf markup_config.Config) converter.Provider {
286 sc := security.DefaultConfig
287 sc.Exec.Allow = security.NewWhitelist("asciidoctor")
288
289 p, err := Provider.New(
290 converter.ProviderConfig{
291 MarkupConfig: mconf,
292 Logger: loggers.NewErrorLogger(),
293 Exec: hexec.New(sc),
294 },
295 )
296 c.Assert(err, qt.IsNil)
297 return p
298 }
299
300 func TestConvert(t *testing.T) {
301 if !Supports() {
302 t.Skip("asciidoctor not installed")
303 }
304 c := qt.New(t)
305
306 p := getProvider(c, markup_config.Default)
307
308 conv, err := p.New(converter.DocumentContext{})
309 c.Assert(err, qt.IsNil)
310
311 b, err := conv.Convert(converter.RenderContext{Src: []byte("testContent")})
312 c.Assert(err, qt.IsNil)
313 c.Assert(string(b.Bytes()), qt.Equals, "<div class=\"paragraph\">\n<p>testContent</p>\n</div>\n")
314 }
315
316 func TestTableOfContents(t *testing.T) {
317 if !Supports() {
318 t.Skip("asciidoctor not installed")
319 }
320 c := qt.New(t)
321 p := getProvider(c, markup_config.Default)
322
323 conv, err := p.New(converter.DocumentContext{})
324 c.Assert(err, qt.IsNil)
325 r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: macro
326 :toclevels: 4
327 toc::[]
328
329 === Introduction
330
331 == Section 1
332
333 === Section 1.1
334
335 ==== Section 1.1.1
336
337 === Section 1.2
338
339 testContent
340
341 == Section 2
342 `)})
343 c.Assert(err, qt.IsNil)
344 toc, ok := r.(converter.TableOfContentsProvider)
345 c.Assert(ok, qt.Equals, true)
346 expected := tableofcontents.Root{
347 Headings: tableofcontents.Headings{
348 {
349 ID: "",
350 Text: "",
351 Headings: tableofcontents.Headings{
352 {
353 ID: "_introduction",
354 Text: "Introduction",
355 Headings: nil,
356 },
357 {
358 ID: "_section_1",
359 Text: "Section 1",
360 Headings: tableofcontents.Headings{
361 {
362 ID: "_section_1_1",
363 Text: "Section 1.1",
364 Headings: tableofcontents.Headings{
365 {
366 ID: "_section_1_1_1",
367 Text: "Section 1.1.1",
368 Headings: nil,
369 },
370 },
371 },
372 {
373 ID: "_section_1_2",
374 Text: "Section 1.2",
375 Headings: nil,
376 },
377 },
378 },
379 {
380 ID: "_section_2",
381 Text: "Section 2",
382 Headings: nil,
383 },
384 },
385 },
386 },
387 }
388 c.Assert(toc.TableOfContents(), qt.DeepEquals, expected)
389 c.Assert(string(r.Bytes()), qt.Not(qt.Contains), "<div id=\"toc\" class=\"toc\">")
390 }
391
392 func TestTableOfContentsWithCode(t *testing.T) {
393 if !Supports() {
394 t.Skip("asciidoctor not installed")
395 }
396 c := qt.New(t)
397 p := getProvider(c, markup_config.Default)
398 conv, err := p.New(converter.DocumentContext{})
399 c.Assert(err, qt.IsNil)
400 r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: auto
401
402 == Some ` + "`code`" + ` in the title
403 `)})
404 c.Assert(err, qt.IsNil)
405 toc, ok := r.(converter.TableOfContentsProvider)
406 c.Assert(ok, qt.Equals, true)
407 expected := tableofcontents.Root{
408 Headings: tableofcontents.Headings{
409 {
410 ID: "",
411 Text: "",
412 Headings: tableofcontents.Headings{
413 {
414 ID: "_some_code_in_the_title",
415 Text: "Some <code>code</code> in the title",
416 Headings: nil,
417 },
418 },
419 },
420 },
421 }
422 c.Assert(toc.TableOfContents(), qt.DeepEquals, expected)
423 c.Assert(string(r.Bytes()), qt.Not(qt.Contains), "<div id=\"toc\" class=\"toc\">")
424 }
425
426 func TestTableOfContentsPreserveTOC(t *testing.T) {
427 if !Supports() {
428 t.Skip("asciidoctor not installed")
429 }
430 c := qt.New(t)
431 mconf := markup_config.Default
432 mconf.AsciidocExt.PreserveTOC = true
433 p := getProvider(c, mconf)
434
435 conv, err := p.New(converter.DocumentContext{})
436 c.Assert(err, qt.IsNil)
437 r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc:
438 :idprefix:
439 :idseparator: -
440
441 == Some title
442 `)})
443 c.Assert(err, qt.IsNil)
444 toc, ok := r.(converter.TableOfContentsProvider)
445 c.Assert(ok, qt.Equals, true)
446 expected := tableofcontents.Root{
447 Headings: tableofcontents.Headings{
448 {
449 ID: "",
450 Text: "",
451 Headings: tableofcontents.Headings{
452 {
453 ID: "some-title",
454 Text: "Some title",
455 Headings: nil,
456 },
457 },
458 },
459 },
460 }
461 c.Assert(toc.TableOfContents(), qt.DeepEquals, expected)
462 c.Assert(string(r.Bytes()), qt.Contains, "<div id=\"toc\" class=\"toc\">")
463 }