publisher.go (5239B)
1 // Copyright 2020 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 publisher 15 16 import ( 17 "errors" 18 "fmt" 19 "io" 20 "net/url" 21 "sync/atomic" 22 23 "github.com/gohugoio/hugo/resources" 24 25 "github.com/gohugoio/hugo/media" 26 27 "github.com/gohugoio/hugo/minifiers" 28 29 bp "github.com/gohugoio/hugo/bufferpool" 30 "github.com/gohugoio/hugo/helpers" 31 32 "github.com/spf13/afero" 33 34 "github.com/gohugoio/hugo/output" 35 "github.com/gohugoio/hugo/transform" 36 "github.com/gohugoio/hugo/transform/livereloadinject" 37 "github.com/gohugoio/hugo/transform/metainject" 38 "github.com/gohugoio/hugo/transform/urlreplacers" 39 ) 40 41 // Descriptor describes the needed publishing chain for an item. 42 type Descriptor struct { 43 // The content to publish. 44 Src io.Reader 45 46 // The OutputFormat of the this content. 47 OutputFormat output.Format 48 49 // Where to publish this content. This is a filesystem-relative path. 50 TargetPath string 51 52 // Counter for the end build summary. 53 StatCounter *uint64 54 55 // Configuration that trigger pre-processing. 56 // LiveReload script will be injected if this is != nil 57 LiveReloadBaseURL *url.URL 58 59 // Enable to inject the Hugo generated tag in the header. Is currently only 60 // injected on the home page for HTML type of output formats. 61 AddHugoGeneratorTag bool 62 63 // If set, will replace all relative URLs with this one. 64 AbsURLPath string 65 66 // Enable to minify the output using the OutputFormat defined above to 67 // pick the correct minifier configuration. 68 Minify bool 69 } 70 71 // DestinationPublisher is the default and currently only publisher in Hugo. This 72 // publisher prepares and publishes an item to the defined destination, e.g. /public. 73 type DestinationPublisher struct { 74 fs afero.Fs 75 min minifiers.Client 76 htmlElementsCollector *htmlElementsCollector 77 } 78 79 // NewDestinationPublisher creates a new DestinationPublisher. 80 func NewDestinationPublisher(rs *resources.Spec, outputFormats output.Formats, mediaTypes media.Types) (pub DestinationPublisher, err error) { 81 fs := rs.BaseFs.PublishFs 82 cfg := rs.Cfg 83 var classCollector *htmlElementsCollector 84 if rs.BuildConfig.WriteStats { 85 classCollector = newHTMLElementsCollector() 86 } 87 pub = DestinationPublisher{fs: fs, htmlElementsCollector: classCollector} 88 pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg) 89 return 90 } 91 92 // Publish applies any relevant transformations and writes the file 93 // to its destination, e.g. /public. 94 func (p DestinationPublisher) Publish(d Descriptor) error { 95 if d.TargetPath == "" { 96 return errors.New("Publish: must provide a TargetPath") 97 } 98 99 src := d.Src 100 101 transformers := p.createTransformerChain(d) 102 103 if len(transformers) != 0 { 104 b := bp.GetBuffer() 105 defer bp.PutBuffer(b) 106 107 if err := transformers.Apply(b, d.Src); err != nil { 108 return fmt.Errorf("failed to process %q: %w", d.TargetPath, err) 109 } 110 111 // This is now what we write to disk. 112 src = b 113 } 114 115 f, err := helpers.OpenFileForWriting(p.fs, d.TargetPath) 116 if err != nil { 117 return err 118 } 119 defer f.Close() 120 121 var w io.Writer = f 122 123 if p.htmlElementsCollector != nil && d.OutputFormat.IsHTML { 124 w = io.MultiWriter(w, newHTMLElementsCollectorWriter(p.htmlElementsCollector)) 125 } 126 127 _, err = io.Copy(w, src) 128 if err == nil && d.StatCounter != nil { 129 atomic.AddUint64(d.StatCounter, uint64(1)) 130 } 131 132 return err 133 } 134 135 func (p DestinationPublisher) PublishStats() PublishStats { 136 if p.htmlElementsCollector == nil { 137 return PublishStats{} 138 } 139 140 return PublishStats{ 141 HTMLElements: p.htmlElementsCollector.getHTMLElements(), 142 } 143 } 144 145 type PublishStats struct { 146 HTMLElements HTMLElements `json:"htmlElements"` 147 } 148 149 // Publisher publishes a result file. 150 type Publisher interface { 151 Publish(d Descriptor) error 152 PublishStats() PublishStats 153 } 154 155 // XML transformer := transform.New(urlreplacers.NewAbsURLInXMLTransformer(path)) 156 func (p DestinationPublisher) createTransformerChain(f Descriptor) transform.Chain { 157 transformers := transform.NewEmpty() 158 159 isHTML := f.OutputFormat.IsHTML 160 161 if f.AbsURLPath != "" { 162 if isHTML { 163 transformers = append(transformers, urlreplacers.NewAbsURLTransformer(f.AbsURLPath)) 164 } else { 165 // Assume XML. 166 transformers = append(transformers, urlreplacers.NewAbsURLInXMLTransformer(f.AbsURLPath)) 167 } 168 } 169 170 if isHTML { 171 if f.LiveReloadBaseURL != nil { 172 transformers = append(transformers, livereloadinject.New(*f.LiveReloadBaseURL)) 173 } 174 175 // This is only injected on the home page. 176 if f.AddHugoGeneratorTag { 177 transformers = append(transformers, metainject.HugoGenerator) 178 } 179 180 } 181 182 if p.min.MinifyOutput { 183 minifyTransformer := p.min.Transformer(f.OutputFormat.MediaType) 184 if minifyTransformer != nil { 185 transformers = append(transformers, minifyTransformer) 186 } 187 } 188 189 return transformers 190 }