github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/markup/asciidocext/convert_test.go (about)

     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/collections"
    25  	"github.com/gohugoio/hugo/common/hexec"
    26  	"github.com/gohugoio/hugo/common/loggers"
    27  	"github.com/gohugoio/hugo/config"
    28  	"github.com/gohugoio/hugo/config/security"
    29  	"github.com/gohugoio/hugo/markup/converter"
    30  	"github.com/gohugoio/hugo/markup/markup_config"
    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  
   347  	c.Assert(toc.TableOfContents().Identifiers, qt.DeepEquals, collections.SortedStringSlice{"_introduction", "_section_1", "_section_1_1", "_section_1_1_1", "_section_1_2", "_section_2"})
   348  	c.Assert(string(r.Bytes()), qt.Not(qt.Contains), "<div id=\"toc\" class=\"toc\">")
   349  }
   350  
   351  func TestTableOfContentsWithCode(t *testing.T) {
   352  	if !Supports() {
   353  		t.Skip("asciidoctor not installed")
   354  	}
   355  	c := qt.New(t)
   356  	p := getProvider(c, markup_config.Default)
   357  	conv, err := p.New(converter.DocumentContext{})
   358  	c.Assert(err, qt.IsNil)
   359  	r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: auto
   360  
   361  == Some ` + "`code`" + ` in the title
   362  `)})
   363  	c.Assert(err, qt.IsNil)
   364  	toc, ok := r.(converter.TableOfContentsProvider)
   365  	c.Assert(ok, qt.Equals, true)
   366  	c.Assert(toc.TableOfContents().HeadingsMap["_some_code_in_the_title"].Title, qt.Equals, "Some <code>code</code> in the title")
   367  	c.Assert(string(r.Bytes()), qt.Not(qt.Contains), "<div id=\"toc\" class=\"toc\">")
   368  }
   369  
   370  func TestTableOfContentsPreserveTOC(t *testing.T) {
   371  	if !Supports() {
   372  		t.Skip("asciidoctor not installed")
   373  	}
   374  	c := qt.New(t)
   375  	mconf := markup_config.Default
   376  	mconf.AsciidocExt.PreserveTOC = true
   377  	p := getProvider(c, mconf)
   378  
   379  	conv, err := p.New(converter.DocumentContext{})
   380  	c.Assert(err, qt.IsNil)
   381  	r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc:
   382  :idprefix:
   383  :idseparator: -
   384  
   385  == Some title
   386  `)})
   387  	c.Assert(err, qt.IsNil)
   388  	toc, ok := r.(converter.TableOfContentsProvider)
   389  	c.Assert(ok, qt.Equals, true)
   390  
   391  	c.Assert(toc.TableOfContents().Identifiers, qt.DeepEquals, collections.SortedStringSlice{"some-title"})
   392  	c.Assert(string(r.Bytes()), qt.Contains, "<div id=\"toc\" class=\"toc\">")
   393  }