generate_page_wrappers.go (7315B)
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 page_generate
15
16 import (
17 "bytes"
18 "fmt"
19 "os"
20 "path/filepath"
21 "reflect"
22
23 "errors"
24
25 "github.com/gohugoio/hugo/common/maps"
26
27 "github.com/gohugoio/hugo/codegen"
28 "github.com/gohugoio/hugo/resources/page"
29 "github.com/gohugoio/hugo/resources/resource"
30 "github.com/gohugoio/hugo/source"
31 )
32
33 const header = `// Copyright 2019 The Hugo Authors. All rights reserved.
34 //
35 // Licensed under the Apache License, Version 2.0 (the "License");
36 // you may not use this file except in compliance with the License.
37 // You may obtain a copy of the License at
38 // http://www.apache.org/licenses/LICENSE-2.0
39 //
40 // Unless required by applicable law or agreed to in writing, software
41 // distributed under the License is distributed on an "AS IS" BASIS,
42 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43 // See the License for the specific language governing permissions and
44 // limitations under the License.
45
46 // This file is autogenerated.
47 `
48
49 var (
50 pageInterfaceDeprecated = reflect.TypeOf((*page.DeprecatedWarningPageMethods)(nil)).Elem()
51 pageInterface = reflect.TypeOf((*page.Page)(nil)).Elem()
52
53 packageDir = filepath.FromSlash("resources/page")
54 )
55
56 func Generate(c *codegen.Inspector) error {
57 if err := generateMarshalJSON(c); err != nil {
58 return fmt.Errorf("failed to generate JSON marshaler: %w", err)
59 }
60
61 if err := generateDeprecatedWrappers(c); err != nil {
62 return fmt.Errorf("failed to generate deprecate wrappers: %w", err)
63 }
64
65 if err := generateFileIsZeroWrappers(c); err != nil {
66 return fmt.Errorf("failed to generate file wrappers: %w", err)
67 }
68
69 return nil
70 }
71
72 func generateMarshalJSON(c *codegen.Inspector) error {
73 filename := filepath.Join(c.ProjectRootDir, packageDir, "page_marshaljson.autogen.go")
74 f, err := os.Create(filename)
75 if err != nil {
76 return err
77 }
78 defer f.Close()
79
80 includes := []reflect.Type{pageInterface}
81
82 // Exclude these methods
83 excludes := []reflect.Type{
84 // We need to evaluate the deprecated vs JSON in the future,
85 // but leave them out for now.
86 pageInterfaceDeprecated,
87
88 // Leave this out for now. We need to revisit the author issue.
89 reflect.TypeOf((*page.AuthorProvider)(nil)).Elem(),
90
91 reflect.TypeOf((*resource.ErrProvider)(nil)).Elem(),
92
93 // navigation.PageMenus
94
95 // Prevent loops.
96 reflect.TypeOf((*page.SitesProvider)(nil)).Elem(),
97 reflect.TypeOf((*page.Positioner)(nil)).Elem(),
98
99 reflect.TypeOf((*page.ChildCareProvider)(nil)).Elem(),
100 reflect.TypeOf((*page.TreeProvider)(nil)).Elem(),
101 reflect.TypeOf((*page.InSectionPositioner)(nil)).Elem(),
102 reflect.TypeOf((*page.PaginatorProvider)(nil)).Elem(),
103 reflect.TypeOf((*maps.Scratcher)(nil)).Elem(),
104 }
105
106 methods := c.MethodsFromTypes(
107 includes,
108 excludes)
109
110 if len(methods) == 0 {
111 return errors.New("no methods found")
112 }
113
114 marshalJSON, pkgImports := methods.ToMarshalJSON(
115 "Page",
116 "github.com/gohugoio/hugo/resources/page",
117 // Exclusion regexps. Matches method names.
118 `\bPage\b`,
119 )
120
121 fmt.Fprintf(f, `%s
122
123 package page
124
125 %s
126
127
128 %s
129
130
131 `, header, importsString(pkgImports), marshalJSON)
132
133 return nil
134 }
135
136 func generateDeprecatedWrappers(c *codegen.Inspector) error {
137 filename := filepath.Join(c.ProjectRootDir, packageDir, "page_wrappers.autogen.go")
138 f, err := os.Create(filename)
139 if err != nil {
140 return err
141 }
142 defer f.Close()
143
144 // Generate a wrapper for deprecated page methods
145
146 reasons := map[string]string{
147 "IsDraft": "Use .Draft.",
148 "Hugo": "Use the global hugo function.",
149 "LanguagePrefix": "Use .Site.LanguagePrefix.",
150 "GetParam": "Use .Param or .Params.myParam.",
151 "RSSLink": `Use the Output Format's link, e.g. something like:
152 {{ with .OutputFormats.Get "RSS" }}{{ .RelPermalink }}{{ end }}`,
153 "URL": "Use .Permalink or .RelPermalink. If what you want is the front matter URL value, use .Params.url",
154 }
155
156 deprecated := func(name string, tp reflect.Type) string {
157 alternative, found := reasons[name]
158 if !found {
159 panic(fmt.Sprintf("no deprecated reason found for %q", name))
160 }
161
162 return fmt.Sprintf("helpers.Deprecated(%q, %q, true)", "Page."+name, alternative)
163 }
164
165 var buff bytes.Buffer
166
167 methods := c.MethodsFromTypes([]reflect.Type{pageInterfaceDeprecated}, nil)
168
169 for _, m := range methods {
170 fmt.Fprint(&buff, m.Declaration("*pageDeprecated"))
171 fmt.Fprintln(&buff, " {")
172 fmt.Fprintf(&buff, "\t%s\n", deprecated(m.Name, m.Owner))
173 fmt.Fprintf(&buff, "\t%s\n}\n", m.Delegate("p", "p"))
174
175 }
176
177 pkgImports := methods.Imports()
178 // pkgImports := append(methods.Imports(), "github.com/gohugoio/hugo/helpers")
179
180 fmt.Fprintf(f, `%s
181
182 package page
183
184 %s
185 // NewDeprecatedWarningPage adds deprecation warnings to the given implementation.
186 func NewDeprecatedWarningPage(p DeprecatedWarningPageMethods) DeprecatedWarningPageMethods {
187 return &pageDeprecated{p: p}
188 }
189
190 type pageDeprecated struct {
191 p DeprecatedWarningPageMethods
192 }
193
194 %s
195
196 `, header, importsString(pkgImports), buff.String())
197
198 return nil
199 }
200
201 func generateFileIsZeroWrappers(c *codegen.Inspector) error {
202 filename := filepath.Join(c.ProjectRootDir, packageDir, "zero_file.autogen.go")
203 f, err := os.Create(filename)
204 if err != nil {
205 return err
206 }
207 defer f.Close()
208
209 // Generate warnings for zero file access
210
211 warning := func(name string, tp reflect.Type) string {
212 msg := fmt.Sprintf(".File.%s on zero object. Wrap it in if or with: {{ with .File }}{{ .%s }}{{ end }}", name, name)
213
214 // We made this a Warning in 0.92.0.
215 // When we remove this construct in 0.93.0, people will get a nil pointer.
216 return fmt.Sprintf("z.log.Warnln(%q)", msg)
217 }
218
219 var buff bytes.Buffer
220
221 methods := c.MethodsFromTypes([]reflect.Type{reflect.TypeOf((*source.File)(nil)).Elem()}, nil)
222
223 for _, m := range methods {
224 if m.Name == "IsZero" {
225 continue
226 }
227 fmt.Fprint(&buff, m.DeclarationNamed("zeroFile"))
228 fmt.Fprintln(&buff, " {")
229 fmt.Fprintf(&buff, "\t%s\n", warning(m.Name, m.Owner))
230 if len(m.Out) > 0 {
231 fmt.Fprintln(&buff, "\treturn")
232 }
233 fmt.Fprintln(&buff, "}")
234
235 }
236
237 pkgImports := append(methods.Imports(), "github.com/gohugoio/hugo/common/loggers", "github.com/gohugoio/hugo/source")
238
239 fmt.Fprintf(f, `%s
240
241 package page
242
243 %s
244
245 // ZeroFile represents a zero value of source.File with warnings if invoked.
246 type zeroFile struct {
247 log loggers.Logger
248 }
249
250 func NewZeroFile(log loggers.Logger) source.File {
251 return zeroFile{log: log}
252 }
253
254 func (zeroFile) IsZero() bool {
255 return true
256 }
257
258 %s
259
260 `, header, importsString(pkgImports), buff.String())
261
262 return nil
263 }
264
265 func importsString(imps []string) string {
266 if len(imps) == 0 {
267 return ""
268 }
269
270 if len(imps) == 1 {
271 return fmt.Sprintf("import %q", imps[0])
272 }
273
274 impsStr := "import (\n"
275 for _, imp := range imps {
276 impsStr += fmt.Sprintf("%q\n", imp)
277 }
278
279 return impsStr + ")"
280 }