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 }