github.com/neohugo/neohugo@v0.123.8/resources/resource_factories/create/create.go (about)

     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 create contains functions for to create Resource objects. This will
    15  // typically non-files.
    16  package create
    17  
    18  import (
    19  	"net/http"
    20  	"os"
    21  	"path"
    22  	"path/filepath"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/neohugo/neohugo/helpers"
    27  	"github.com/neohugo/neohugo/hugofs/glob"
    28  	"github.com/neohugo/neohugo/identity"
    29  
    30  	"github.com/neohugo/neohugo/hugofs"
    31  
    32  	"github.com/neohugo/neohugo/cache/dynacache"
    33  	"github.com/neohugo/neohugo/cache/filecache"
    34  	"github.com/neohugo/neohugo/common/hugio"
    35  	"github.com/neohugo/neohugo/resources"
    36  	"github.com/neohugo/neohugo/resources/resource"
    37  )
    38  
    39  // Client contains methods to create Resource objects.
    40  // tasks to Resource objects.
    41  type Client struct {
    42  	rs               *resources.Spec
    43  	httpClient       *http.Client
    44  	cacheGetResource *filecache.Cache
    45  }
    46  
    47  // New creates a new Client with the given specification.
    48  func New(rs *resources.Spec) *Client {
    49  	return &Client{
    50  		rs: rs,
    51  		httpClient: &http.Client{
    52  			Timeout: time.Minute,
    53  		},
    54  		cacheGetResource: rs.FileCaches.GetResourceCache(),
    55  	}
    56  }
    57  
    58  // Copy copies r to the new targetPath.
    59  func (c *Client) Copy(r resource.Resource, targetPath string) (resource.Resource, error) {
    60  	key := dynacache.CleanKey(targetPath)
    61  	return c.rs.ResourceCache.GetOrCreate(key, func() (resource.Resource, error) {
    62  		return resources.Copy(r, targetPath), nil
    63  	})
    64  }
    65  
    66  // Get creates a new Resource by opening the given pathname in the assets filesystem.
    67  func (c *Client) Get(pathname string) (resource.Resource, error) {
    68  	pathname = path.Clean(pathname)
    69  	key := dynacache.CleanKey(pathname)
    70  
    71  	return c.rs.ResourceCache.GetOrCreate(key, func() (resource.Resource, error) {
    72  		// The resource file will not be read before it gets used (e.g. in .Content),
    73  		// so we need to check that the file exists here.
    74  		filename := filepath.FromSlash(pathname)
    75  		fi, err := c.rs.BaseFs.Assets.Fs.Stat(filename)
    76  		if err != nil {
    77  			if os.IsNotExist(err) {
    78  				return nil, nil
    79  			}
    80  			// A real error.
    81  			return nil, err
    82  		}
    83  
    84  		pi := fi.(hugofs.FileMetaInfo).Meta().PathInfo
    85  
    86  		return c.rs.NewResource(resources.ResourceSourceDescriptor{
    87  			LazyPublish: true,
    88  			OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
    89  				return c.rs.BaseFs.Assets.Fs.Open(filename)
    90  			},
    91  			Path:          pi,
    92  			GroupIdentity: pi,
    93  			TargetPath:    pathname,
    94  		})
    95  	})
    96  }
    97  
    98  // Match gets the resources matching the given pattern from the assets filesystem.
    99  func (c *Client) Match(pattern string) (resource.Resources, error) {
   100  	return c.match("__match", pattern, nil, false)
   101  }
   102  
   103  func (c *Client) ByType(tp string) resource.Resources {
   104  	res, err := c.match(path.Join("_byType", tp), "**", func(r resource.Resource) bool { return r.ResourceType() == tp }, false)
   105  	if err != nil {
   106  		panic(err)
   107  	}
   108  	return res
   109  }
   110  
   111  // GetMatch gets first resource matching the given pattern from the assets filesystem.
   112  func (c *Client) GetMatch(pattern string) (resource.Resource, error) {
   113  	res, err := c.match("__get-match", pattern, nil, true)
   114  	if err != nil || len(res) == 0 {
   115  		return nil, err
   116  	}
   117  	return res[0], err
   118  }
   119  
   120  func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource) bool, firstOnly bool) (resource.Resources, error) {
   121  	pattern = glob.NormalizePath(pattern)
   122  	partitions := glob.FilterGlobParts(strings.Split(pattern, "/"))
   123  	key := path.Join(name, path.Join(partitions...))
   124  	key = path.Join(key, pattern)
   125  
   126  	return c.rs.ResourceCache.GetOrCreateResources(key, func() (resource.Resources, error) {
   127  		var res resource.Resources
   128  
   129  		handle := func(info hugofs.FileMetaInfo) (bool, error) {
   130  			meta := info.Meta()
   131  
   132  			r, err := c.rs.NewResource(resources.ResourceSourceDescriptor{
   133  				LazyPublish: true,
   134  				OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
   135  					return meta.Open()
   136  				},
   137  				NameNormalized: meta.PathInfo.Name(),
   138  				NameOriginal:   meta.PathInfo.Unnormalized().Name(),
   139  				GroupIdentity:  meta.PathInfo,
   140  				TargetPath:     meta.PathInfo.Unnormalized().Path(),
   141  			})
   142  			if err != nil {
   143  				return true, err
   144  			}
   145  
   146  			if matchFunc != nil && !matchFunc(r) {
   147  				return false, nil
   148  			}
   149  
   150  			res = append(res, r)
   151  
   152  			return firstOnly, nil
   153  		}
   154  
   155  		if err := hugofs.Glob(c.rs.BaseFs.Assets.Fs, pattern, handle); err != nil {
   156  			return nil, err
   157  		}
   158  
   159  		return res, nil
   160  	})
   161  }
   162  
   163  // FromString creates a new Resource from a string with the given relative target path.
   164  // TODO(bep) see #10912; we currently emit a warning for this config scenario.
   165  func (c *Client) FromString(targetPath, content string) (resource.Resource, error) {
   166  	targetPath = path.Clean(targetPath)
   167  	key := dynacache.CleanKey(targetPath) + helpers.MD5String(content)
   168  	r, err := c.rs.ResourceCache.GetOrCreate(key, func() (resource.Resource, error) {
   169  		return c.rs.NewResource(
   170  			resources.ResourceSourceDescriptor{
   171  				LazyPublish:   true,
   172  				GroupIdentity: identity.Anonymous, // All usage of this resource are tracked via its string content.
   173  				OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
   174  					return hugio.NewReadSeekerNoOpCloserFromString(content), nil
   175  				},
   176  				TargetPath: targetPath,
   177  			})
   178  	})
   179  
   180  	return r, err
   181  }