github.com/pietrocarrara/hugo@v0.47.1/resource/bundler/bundler.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 bundler contains functions for concatenation etc. of Resource objects.
    15  package bundler
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"path/filepath"
    21  
    22  	"github.com/gohugoio/hugo/media"
    23  	"github.com/gohugoio/hugo/resource"
    24  )
    25  
    26  // Client contains methods perform concatenation and other bundling related
    27  // tasks to Resource objects.
    28  type Client struct {
    29  	rs *resource.Spec
    30  }
    31  
    32  // New creates a new Client with the given specification.
    33  func New(rs *resource.Spec) *Client {
    34  	return &Client{rs: rs}
    35  }
    36  
    37  type multiReadSeekCloser struct {
    38  	mr      io.Reader
    39  	sources []resource.ReadSeekCloser
    40  }
    41  
    42  func (r *multiReadSeekCloser) Read(p []byte) (n int, err error) {
    43  	return r.mr.Read(p)
    44  }
    45  
    46  func (r *multiReadSeekCloser) Seek(offset int64, whence int) (newOffset int64, err error) {
    47  	for _, s := range r.sources {
    48  		newOffset, err = s.Seek(offset, whence)
    49  		if err != nil {
    50  			return
    51  		}
    52  	}
    53  	return
    54  }
    55  
    56  func (r *multiReadSeekCloser) Close() error {
    57  	for _, s := range r.sources {
    58  		s.Close()
    59  	}
    60  	return nil
    61  }
    62  
    63  // Concat concatenates the list of Resource objects.
    64  func (c *Client) Concat(targetPath string, resources resource.Resources) (resource.Resource, error) {
    65  	// The CACHE_OTHER will make sure this will be re-created and published on rebuilds.
    66  	return c.rs.ResourceCache.GetOrCreate(resource.CACHE_OTHER, targetPath, func() (resource.Resource, error) {
    67  		var resolvedm media.Type
    68  
    69  		// The given set of resources must be of the same Media Type.
    70  		// We may improve on that in the future, but then we need to know more.
    71  		for i, r := range resources {
    72  			if i > 0 && r.MediaType().Type() != resolvedm.Type() {
    73  				return nil, fmt.Errorf("resources in Concat must be of the same Media Type, got %q and %q", r.MediaType().Type(), resolvedm.Type())
    74  			}
    75  			resolvedm = r.MediaType()
    76  		}
    77  
    78  		concatr := func() (resource.ReadSeekCloser, error) {
    79  			var rcsources []resource.ReadSeekCloser
    80  			for _, s := range resources {
    81  				rcr, ok := s.(resource.ReadSeekCloserResource)
    82  				if !ok {
    83  					return nil, fmt.Errorf("resource %T does not implement resource.ReadSeekerCloserResource", s)
    84  				}
    85  				rc, err := rcr.ReadSeekCloser()
    86  				if err != nil {
    87  					// Close the already opened.
    88  					for _, rcs := range rcsources {
    89  						rcs.Close()
    90  					}
    91  					return nil, err
    92  				}
    93  				rcsources = append(rcsources, rc)
    94  			}
    95  
    96  			readers := make([]io.Reader, len(rcsources))
    97  			for i := 0; i < len(rcsources); i++ {
    98  				readers[i] = rcsources[i]
    99  			}
   100  
   101  			mr := io.MultiReader(readers...)
   102  
   103  			return &multiReadSeekCloser{mr: mr, sources: rcsources}, nil
   104  		}
   105  
   106  		composite, err := c.rs.NewForFs(
   107  			c.rs.BaseFs.Resources.Fs,
   108  			resource.ResourceSourceDescriptor{
   109  				LazyPublish:        true,
   110  				OpenReadSeekCloser: concatr,
   111  				RelTargetFilename:  filepath.Clean(targetPath)})
   112  
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  
   117  		return composite, nil
   118  	})
   119  
   120  }