github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/transform/chain.go (about)

     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  	"os"
    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 := os.CreateTemp("", "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  }