chain.go (3238B)
1 // Copyright 2018 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 transform
15
16 import (
17 "bytes"
18 "io"
19 "io/ioutil"
20
21 bp "github.com/gohugoio/hugo/bufferpool"
22 "github.com/gohugoio/hugo/common/herrors"
23 "github.com/gohugoio/hugo/hugofs"
24 )
25
26 // Transformer is the func that needs to be implemented by a transformation step.
27 type Transformer func(ft FromTo) error
28
29 // BytesReader wraps the Bytes method, usually implemented by bytes.Buffer, and an
30 // io.Reader.
31 type BytesReader interface {
32 // The slice given by Bytes is valid for use only until the next buffer modification.
33 // That is, if you want to use this value outside of the current transformer step,
34 // you need to take a copy.
35 Bytes() []byte
36
37 io.Reader
38 }
39
40 // FromTo is sent to each transformation step in the chain.
41 type FromTo interface {
42 From() BytesReader
43 To() io.Writer
44 }
45
46 // Chain is an ordered processing chain. The next transform operation will
47 // receive the output from the previous.
48 type Chain []Transformer
49
50 // New creates a content transformer chain given the provided transform funcs.
51 func New(trs ...Transformer) Chain {
52 return trs
53 }
54
55 // NewEmpty creates a new slice of transformers with a capacity of 20.
56 func NewEmpty() Chain {
57 return make(Chain, 0, 20)
58 }
59
60 // Implements contentTransformer
61 // Content is read from the from-buffer and rewritten to to the to-buffer.
62 type fromToBuffer struct {
63 from *bytes.Buffer
64 to *bytes.Buffer
65 }
66
67 func (ft fromToBuffer) From() BytesReader {
68 return ft.from
69 }
70
71 func (ft fromToBuffer) To() io.Writer {
72 return ft.to
73 }
74
75 // Apply passes the given from io.Reader through the transformation chain.
76 // The result is written to to.
77 func (c *Chain) Apply(to io.Writer, from io.Reader) error {
78 if len(*c) == 0 {
79 _, err := io.Copy(to, from)
80 return err
81 }
82
83 b1 := bp.GetBuffer()
84 defer bp.PutBuffer(b1)
85
86 if _, err := b1.ReadFrom(from); err != nil {
87 return err
88 }
89
90 b2 := bp.GetBuffer()
91 defer bp.PutBuffer(b2)
92
93 fb := &fromToBuffer{from: b1, to: b2}
94
95 for i, tr := range *c {
96 if i > 0 {
97 if fb.from == b1 {
98 fb.from = b2
99 fb.to = b1
100 fb.to.Reset()
101 } else {
102 fb.from = b1
103 fb.to = b2
104 fb.to.Reset()
105 }
106 }
107
108 if err := tr(fb); err != nil {
109 // Write output to a temp file so it can be read by the user for trouble shooting.
110 filename := "output.html"
111 tempfile, ferr := ioutil.TempFile("", "hugo-transform-error")
112 if ferr == nil {
113 filename = tempfile.Name()
114 defer tempfile.Close()
115 _, _ = io.Copy(tempfile, fb.from)
116 return herrors.NewFileErrorFromFile(err, filename, hugofs.Os, nil)
117 }
118 return herrors.NewFileErrorFromName(err, filename).UpdateContent(fb.from, nil)
119
120 }
121 }
122
123 _, err := fb.to.WriteTo(to)
124 return err
125 }