github.com/lyeb/hugo@v0.47.1/tpl/resources/resources.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 resources 15 16 import ( 17 "errors" 18 "fmt" 19 "path/filepath" 20 21 "github.com/gohugoio/hugo/deps" 22 "github.com/gohugoio/hugo/resource" 23 "github.com/gohugoio/hugo/resource/bundler" 24 "github.com/gohugoio/hugo/resource/create" 25 "github.com/gohugoio/hugo/resource/integrity" 26 "github.com/gohugoio/hugo/resource/minifier" 27 "github.com/gohugoio/hugo/resource/postcss" 28 "github.com/gohugoio/hugo/resource/templates" 29 "github.com/gohugoio/hugo/resource/tocss/scss" 30 "github.com/spf13/cast" 31 ) 32 33 // New returns a new instance of the resources-namespaced template functions. 34 func New(deps *deps.Deps) (*Namespace, error) { 35 scssClient, err := scss.New(deps.BaseFs.Assets, deps.ResourceSpec) 36 if err != nil { 37 return nil, err 38 } 39 return &Namespace{ 40 deps: deps, 41 scssClient: scssClient, 42 createClient: create.New(deps.ResourceSpec), 43 bundlerClient: bundler.New(deps.ResourceSpec), 44 integrityClient: integrity.New(deps.ResourceSpec), 45 minifyClient: minifier.New(deps.ResourceSpec), 46 postcssClient: postcss.New(deps.ResourceSpec), 47 templatesClient: templates.New(deps.ResourceSpec, deps.TextTmpl), 48 }, nil 49 } 50 51 // Namespace provides template functions for the "resources" namespace. 52 type Namespace struct { 53 deps *deps.Deps 54 55 createClient *create.Client 56 bundlerClient *bundler.Client 57 scssClient *scss.Client 58 integrityClient *integrity.Client 59 minifyClient *minifier.Client 60 postcssClient *postcss.Client 61 templatesClient *templates.Client 62 } 63 64 // Get locates the filename given in Hugo's filesystems: static, assets and content (in that order) 65 // and creates a Resource object that can be used for further transformations. 66 func (ns *Namespace) Get(filename interface{}) (resource.Resource, error) { 67 filenamestr, err := cast.ToStringE(filename) 68 if err != nil { 69 return nil, err 70 } 71 72 filenamestr = filepath.Clean(filenamestr) 73 74 // Resource Get'ing is currently limited to /assets to make it simpler 75 // to control the behaviour of publishing and partial rebuilding. 76 return ns.createClient.Get(ns.deps.BaseFs.Assets.Fs, filenamestr) 77 78 } 79 80 // Concat concatenates a slice of Resource objects. These resources must 81 // (currently) be of the same Media Type. 82 func (ns *Namespace) Concat(targetPathIn interface{}, r interface{}) (resource.Resource, error) { 83 targetPath, err := cast.ToStringE(targetPathIn) 84 if err != nil { 85 return nil, err 86 } 87 88 var rr resource.Resources 89 90 switch v := r.(type) { 91 // This is what we get from the slice func. 92 case []interface{}: 93 rr = make([]resource.Resource, len(v)) 94 for i := 0; i < len(v); i++ { 95 rv, ok := v[i].(resource.Resource) 96 if !ok { 97 return nil, fmt.Errorf("cannot concat type %T", v[i]) 98 } 99 rr[i] = rv 100 } 101 // This is what we get from .Resources.Match etc. 102 case resource.Resources: 103 rr = v 104 default: 105 // We may support Page collections at one point, but we need to think about ... 106 // what to acutually concatenate. 107 return nil, fmt.Errorf("slice %T not supported in concat", r) 108 } 109 110 if len(rr) == 0 { 111 return nil, errors.New("must provide one or more Resource objects to concat") 112 } 113 114 return ns.bundlerClient.Concat(targetPath, rr) 115 } 116 117 // FromString creates a Resource from a string published to the relative target path. 118 func (ns *Namespace) FromString(targetPathIn, contentIn interface{}) (resource.Resource, error) { 119 targetPath, err := cast.ToStringE(targetPathIn) 120 if err != nil { 121 return nil, err 122 } 123 content, err := cast.ToStringE(contentIn) 124 if err != nil { 125 return nil, err 126 } 127 128 return ns.createClient.FromString(targetPath, content) 129 } 130 131 // ExecuteAsTemplate creates a Resource from a Go template, parsed and executed with 132 // the given data, and published to the relative target path. 133 func (ns *Namespace) ExecuteAsTemplate(args ...interface{}) (resource.Resource, error) { 134 if len(args) != 3 { 135 return nil, fmt.Errorf("must provide targetPath, the template data context and a Resource object") 136 } 137 targetPath, err := cast.ToStringE(args[0]) 138 if err != nil { 139 return nil, err 140 } 141 data := args[1] 142 143 r, ok := args[2].(resource.Resource) 144 if !ok { 145 return nil, fmt.Errorf("type %T not supported in Resource transformations", args[2]) 146 } 147 148 return ns.templatesClient.ExecuteAsTemplate(r, targetPath, data) 149 } 150 151 // Fingerprint transforms the given Resource with a MD5 hash of the content in 152 // the RelPermalink and Permalink. 153 func (ns *Namespace) Fingerprint(args ...interface{}) (resource.Resource, error) { 154 if len(args) < 1 || len(args) > 2 { 155 return nil, errors.New("must provide a Resource and (optional) crypto algo") 156 } 157 158 var algo string 159 resIdx := 0 160 161 if len(args) == 2 { 162 resIdx = 1 163 var err error 164 algo, err = cast.ToStringE(args[0]) 165 if err != nil { 166 return nil, err 167 } 168 } 169 170 r, ok := args[resIdx].(resource.Resource) 171 if !ok { 172 return nil, fmt.Errorf("%T is not a Resource", args[resIdx]) 173 } 174 175 return ns.integrityClient.Fingerprint(r, algo) 176 } 177 178 // Minify minifies the given Resource using the MediaType to pick the correct 179 // minifier. 180 func (ns *Namespace) Minify(r resource.Resource) (resource.Resource, error) { 181 return ns.minifyClient.Minify(r) 182 } 183 184 // ToCSS converts the given Resource to CSS. You can optional provide an Options 185 // object or a target path (string) as first argument. 186 func (ns *Namespace) ToCSS(args ...interface{}) (resource.Resource, error) { 187 var ( 188 r resource.Resource 189 m map[string]interface{} 190 targetPath string 191 err error 192 ok bool 193 ) 194 195 r, targetPath, ok = ns.resolveIfFirstArgIsString(args) 196 197 if !ok { 198 r, m, err = ns.resolveArgs(args) 199 if err != nil { 200 return nil, err 201 } 202 } 203 204 var options scss.Options 205 if targetPath != "" { 206 options.TargetPath = targetPath 207 } else if m != nil { 208 options, err = scss.DecodeOptions(m) 209 if err != nil { 210 return nil, err 211 } 212 } 213 214 return ns.scssClient.ToCSS(r, options) 215 } 216 217 // PostCSS processes the given Resource with PostCSS 218 func (ns *Namespace) PostCSS(args ...interface{}) (resource.Resource, error) { 219 r, m, err := ns.resolveArgs(args) 220 if err != nil { 221 return nil, err 222 } 223 var options postcss.Options 224 if m != nil { 225 options, err = postcss.DecodeOptions(m) 226 if err != nil { 227 return nil, err 228 } 229 } 230 231 return ns.postcssClient.Process(r, options) 232 } 233 234 // We allow string or a map as the first argument in some cases. 235 func (ns *Namespace) resolveIfFirstArgIsString(args []interface{}) (resource.Resource, string, bool) { 236 if len(args) != 2 { 237 return nil, "", false 238 } 239 240 v1, ok1 := args[0].(string) 241 if !ok1 { 242 return nil, "", false 243 } 244 v2, ok2 := args[1].(resource.Resource) 245 246 return v2, v1, ok2 247 } 248 249 // This roundabout way of doing it is needed to get both pipeline behaviour and options as arguments. 250 func (ns *Namespace) resolveArgs(args []interface{}) (resource.Resource, map[string]interface{}, error) { 251 if len(args) == 0 { 252 return nil, nil, errors.New("no Resource provided in transformation") 253 } 254 255 if len(args) == 1 { 256 r, ok := args[0].(resource.Resource) 257 if !ok { 258 return nil, nil, fmt.Errorf("type %T not supported in Resource transformations", args[0]) 259 } 260 return r, nil, nil 261 } 262 263 r, ok := args[1].(resource.Resource) 264 if !ok { 265 return nil, nil, fmt.Errorf("type %T not supported in Resource transformations", args[0]) 266 } 267 268 m, err := cast.ToStringMapE(args[0]) 269 if err != nil { 270 return nil, nil, fmt.Errorf("invalid options type: %s", err) 271 } 272 273 return r, m, nil 274 }