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

     1  // Copyright 2024 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 resource contains Resource related types.
    15  package resource
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"github.com/neohugo/neohugo/hugofs/glob"
    22  	"github.com/spf13/cast"
    23  )
    24  
    25  var _ ResourceFinder = (*Resources)(nil)
    26  
    27  // Resources represents a slice of resources, which can be a mix of different types.
    28  // I.e. both pages and images etc.
    29  type Resources []Resource
    30  
    31  // var _ resource.ResourceFinder = (*Namespace)(nil)
    32  // ResourcesConverter converts a given slice of Resource objects to Resources.
    33  type ResourcesConverter interface {
    34  	// For internal use.
    35  	ToResources() Resources
    36  }
    37  
    38  // ByType returns resources of a given resource type (e.g. "image").
    39  func (r Resources) ByType(typ any) Resources {
    40  	tpstr, err := cast.ToStringE(typ)
    41  	if err != nil {
    42  		panic(err)
    43  	}
    44  	var filtered Resources
    45  
    46  	for _, resource := range r {
    47  		if resource.ResourceType() == tpstr {
    48  			filtered = append(filtered, resource)
    49  		}
    50  	}
    51  	return filtered
    52  }
    53  
    54  // Get locates the name given in Resources.
    55  // The search is case insensitive.
    56  func (r Resources) Get(name any) Resource {
    57  	if r == nil {
    58  		return nil
    59  	}
    60  	namestr, err := cast.ToStringE(name)
    61  	if err != nil {
    62  		panic(err)
    63  	}
    64  	namestr = strings.ToLower(namestr)
    65  
    66  	// First check the Name.
    67  	// Note that this can be modified by the user in the front matter,
    68  	// also, it does not contain any language code.
    69  	for _, resource := range r {
    70  		if strings.EqualFold(namestr, resource.Name()) {
    71  			return resource
    72  		}
    73  	}
    74  
    75  	// Finally, check the normalized name.
    76  	for _, resource := range r {
    77  		if nop, ok := resource.(NameNormalizedProvider); ok {
    78  			if strings.EqualFold(namestr, nop.NameNormalized()) {
    79  				return resource
    80  			}
    81  		}
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  // GetMatch finds the first Resource matching the given pattern, or nil if none found.
    88  // See Match for a more complete explanation about the rules used.
    89  func (r Resources) GetMatch(pattern any) Resource {
    90  	patternstr, err := cast.ToStringE(pattern)
    91  	if err != nil {
    92  		panic(err)
    93  	}
    94  
    95  	g, err := glob.GetGlob(patternstr)
    96  	if err != nil {
    97  		panic(err)
    98  	}
    99  
   100  	for _, resource := range r {
   101  		if g.Match(resource.Name()) {
   102  			return resource
   103  		}
   104  	}
   105  
   106  	// Finally, check the original name.
   107  	for _, resource := range r {
   108  		if nop, ok := resource.(NameNormalizedProvider); ok {
   109  			if g.Match(nop.NameNormalized()) {
   110  				return resource
   111  			}
   112  		}
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  // Match gets all resources matching the given base filename prefix, e.g
   119  // "*.png" will match all png files. The "*" does not match path delimiters (/),
   120  // so if you organize your resources in sub-folders, you need to be explicit about it, e.g.:
   121  // "images/*.png". To match any PNG image anywhere in the bundle you can do "**.png", and
   122  // to match all PNG images below the images folder, use "images/**.jpg".
   123  // The matching is case insensitive.
   124  // Match matches by using the value of Resource.Name, which, by default, is a filename with
   125  // path relative to the bundle root with Unix style slashes (/) and no leading slash, e.g. "images/logo.png".
   126  // See https://github.com/gobwas/glob for the full rules set.
   127  func (r Resources) Match(pattern any) Resources {
   128  	patternstr, err := cast.ToStringE(pattern)
   129  	if err != nil {
   130  		panic(err)
   131  	}
   132  
   133  	g, err := glob.GetGlob(patternstr)
   134  	if err != nil {
   135  		panic(err)
   136  	}
   137  
   138  	var matches Resources
   139  	for _, resource := range r {
   140  		if g.Match(resource.Name()) {
   141  			matches = append(matches, resource)
   142  		}
   143  	}
   144  	if len(matches) == 0 {
   145  		// 	Fall back to the normalized name.
   146  		for _, resource := range r {
   147  			if nop, ok := resource.(NameNormalizedProvider); ok {
   148  				if g.Match(nop.NameNormalized()) {
   149  					matches = append(matches, resource)
   150  				}
   151  			}
   152  		}
   153  	}
   154  	return matches
   155  }
   156  
   157  type translatedResource interface {
   158  	TranslationKey() string
   159  }
   160  
   161  // MergeByLanguage adds missing translations in r1 from r2.
   162  func (r Resources) MergeByLanguage(r2 Resources) Resources {
   163  	result := append(Resources(nil), r...)
   164  	m := make(map[string]bool)
   165  	for _, rr := range r {
   166  		if translated, ok := rr.(translatedResource); ok {
   167  			m[translated.TranslationKey()] = true
   168  		}
   169  	}
   170  
   171  	for _, rr := range r2 {
   172  		if translated, ok := rr.(translatedResource); ok {
   173  			if _, found := m[translated.TranslationKey()]; !found {
   174  				result = append(result, rr)
   175  			}
   176  		}
   177  	}
   178  	return result
   179  }
   180  
   181  // MergeByLanguageInterface is the generic version of MergeByLanguage. It
   182  // is here just so it can be called from the tpl package.
   183  // This is for internal use.
   184  func (r Resources) MergeByLanguageInterface(in any) (any, error) {
   185  	r2, ok := in.(Resources)
   186  	if !ok {
   187  		return nil, fmt.Errorf("%T cannot be merged by language", in)
   188  	}
   189  	return r.MergeByLanguage(r2), nil
   190  }
   191  
   192  // Source is an internal template and not meant for use in the templates. It
   193  // may change without notice.
   194  type Source interface {
   195  	Publish() error
   196  }
   197  
   198  // ResourceFinder provides methods to find Resources.
   199  // Note that GetRemote (as found in resources.GetRemote) is
   200  // not covered by this interface, as this is only available as a global template function.
   201  type ResourceFinder interface {
   202  	// Get locates the Resource with the given name in the current context (e.g. in .Page.Resources).
   203  	//
   204  	// It returns nil if no Resource could found, panics if name is invalid.
   205  	Get(name any) Resource
   206  
   207  	// GetMatch finds the first Resource matching the given pattern, or nil if none found.
   208  	//
   209  	// See Match for a more complete explanation about the rules used.
   210  	//
   211  	// It returns nil if no Resource could found, panics if pattern is invalid.
   212  	GetMatch(pattern any) Resource
   213  
   214  	// Match gets all resources matching the given base path prefix, e.g
   215  	// "*.png" will match all png files. The "*" does not match path delimiters (/),
   216  	// so if you organize your resources in sub-folders, you need to be explicit about it, e.g.:
   217  	// "images/*.png". To match any PNG image anywhere in the bundle you can do "**.png", and
   218  	// to match all PNG images below the images folder, use "images/**.jpg".
   219  	//
   220  	// The matching is case insensitive.
   221  	//
   222  	// Match matches by using a relative pathwith Unix style slashes (/) and no
   223  	// leading slash, e.g. "images/logo.png".
   224  	//
   225  	// See https://github.com/gobwas/glob for the full rules set.
   226  	//
   227  	// See Match for a more complete explanation about the rules used.
   228  	//
   229  	// It returns nil if no Resources could found, panics if pattern is invalid.
   230  	Match(pattern any) Resources
   231  
   232  	// ByType returns resources of a given resource type (e.g. "image").
   233  	// It returns nil if no Resources could found, panics if typ is invalid.
   234  	ByType(typ any) Resources
   235  }