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 }