convert.go (3913B)
1 // Copyright 2019 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 rst converts content to HTML using the RST external helper. 15 package rst 16 17 import ( 18 "bytes" 19 "runtime" 20 21 "github.com/gohugoio/hugo/common/hexec" 22 "github.com/gohugoio/hugo/htesting" 23 24 "github.com/gohugoio/hugo/identity" 25 "github.com/gohugoio/hugo/markup/internal" 26 27 "github.com/gohugoio/hugo/markup/converter" 28 ) 29 30 // Provider is the package entry point. 31 var Provider converter.ProviderProvider = provider{} 32 33 type provider struct { 34 } 35 36 func (p provider) New(cfg converter.ProviderConfig) (converter.Provider, error) { 37 return converter.NewProvider("rst", func(ctx converter.DocumentContext) (converter.Converter, error) { 38 return &rstConverter{ 39 ctx: ctx, 40 cfg: cfg, 41 }, nil 42 }), nil 43 } 44 45 type rstConverter struct { 46 ctx converter.DocumentContext 47 cfg converter.ProviderConfig 48 } 49 50 func (c *rstConverter) Convert(ctx converter.RenderContext) (converter.Result, error) { 51 b, err := c.getRstContent(ctx.Src, c.ctx) 52 if err != nil { 53 return nil, err 54 } 55 return converter.Bytes(b), nil 56 } 57 58 func (c *rstConverter) Supports(feature identity.Identity) bool { 59 return false 60 } 61 62 // getRstContent calls the Python script rst2html as an external helper 63 // to convert reStructuredText content to HTML. 64 func (c *rstConverter) getRstContent(src []byte, ctx converter.DocumentContext) ([]byte, error) { 65 logger := c.cfg.Logger 66 binaryName, binaryPath := getRstBinaryNameAndPath() 67 68 if binaryName == "" { 69 logger.Println("rst2html / rst2html.py not found in $PATH: Please install.\n", 70 " Leaving reStructuredText content unrendered.") 71 return src, nil 72 } 73 74 logger.Infoln("Rendering", ctx.DocumentName, "with", binaryName, "...") 75 76 var result []byte 77 var err error 78 79 // certain *nix based OSs wrap executables in scripted launchers 80 // invoking binaries on these OSs via python interpreter causes SyntaxError 81 // invoke directly so that shebangs work as expected 82 // handle Windows manually because it doesn't do shebangs 83 if runtime.GOOS == "windows" { 84 pythonBinary, _ := internal.GetPythonBinaryAndExecPath() 85 args := []string{binaryPath, "--leave-comments", "--initial-header-level=2"} 86 result, err = internal.ExternallyRenderContent(c.cfg, ctx, src, pythonBinary, args) 87 } else { 88 args := []string{"--leave-comments", "--initial-header-level=2"} 89 result, err = internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args) 90 } 91 92 if err != nil { 93 return nil, err 94 } 95 96 // TODO(bep) check if rst2html has a body only option. 97 bodyStart := bytes.Index(result, []byte("<body>\n")) 98 if bodyStart < 0 { 99 bodyStart = -7 // compensate for length 100 } 101 102 bodyEnd := bytes.Index(result, []byte("\n</body>")) 103 if bodyEnd < 0 || bodyEnd >= len(result) { 104 bodyEnd = len(result) - 1 105 if bodyEnd < 0 { 106 bodyEnd = 0 107 } 108 } 109 110 return result[bodyStart+7 : bodyEnd], err 111 } 112 113 var rst2Binaries = []string{"rst2html", "rst2html.py"} 114 115 func getRstBinaryNameAndPath() (string, string) { 116 for _, candidate := range rst2Binaries { 117 if pth := hexec.LookPath(candidate); pth != "" { 118 return candidate, pth 119 } 120 } 121 return "", "" 122 } 123 124 // Supports returns whether rst is (or should be) installed on this computer. 125 func Supports() bool { 126 name, _ := getRstBinaryNameAndPath() 127 hasBin := name != "" 128 if htesting.SupportsAll() { 129 if !hasBin { 130 panic("rst not installed") 131 } 132 return true 133 } 134 return hasBin 135 }