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 }