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 }