github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/scripts/fork_go_templates/main.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"github.com/gohugoio/hugo/common/hexec"
    12  
    13  	"github.com/gohugoio/hugo/common/hugio"
    14  
    15  	"github.com/spf13/afero"
    16  )
    17  
    18  func main() {
    19  	// The current is built with de4748c47c67392a57f250714509f590f68ad395 HEAD, tag: go1.20.
    20  	fmt.Println("Forking ...")
    21  	defer fmt.Println("Done ...")
    22  
    23  	cleanFork()
    24  
    25  	htmlRoot := filepath.Join(forkRoot, "htmltemplate")
    26  
    27  	for _, pkg := range goPackages {
    28  		copyGoPackage(pkg.dstPkg, pkg.srcPkg)
    29  	}
    30  
    31  	for _, pkg := range goPackages {
    32  		doWithGoFiles(pkg.dstPkg, pkg.rewriter, pkg.replacer)
    33  	}
    34  
    35  	goimports(htmlRoot)
    36  	gofmt(forkRoot)
    37  }
    38  
    39  const (
    40  	// TODO(bep)
    41  	goSource = "/Users/bep/dev/go/misc/go/src"
    42  	forkRoot = "../../tpl/internal/go_templates"
    43  )
    44  
    45  type goPackage struct {
    46  	srcPkg   string
    47  	dstPkg   string
    48  	replacer func(name, content string) string
    49  	rewriter func(name string)
    50  }
    51  
    52  var (
    53  	textTemplateReplacers = strings.NewReplacer(
    54  		`"text/template/`, `"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/`,
    55  		`"internal/fmtsort"`, `"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"`,
    56  		`"internal/testenv"`, `"github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"`,
    57  		"TestLinkerGC", "_TestLinkerGC",
    58  		// Rename types and function that we want to overload.
    59  		"type state struct", "type stateOld struct",
    60  		"func (s *state) evalFunction", "func (s *state) evalFunctionOld",
    61  		"func (s *state) evalField(", "func (s *state) evalFieldOld(",
    62  		"func (s *state) evalCall(", "func (s *state) evalCallOld(",
    63  		"func isTrue(val reflect.Value) (truth, ok bool) {", "func isTrueOld(val reflect.Value) (truth, ok bool) {",
    64  	)
    65  
    66  	testEnvReplacers = strings.NewReplacer(
    67  		`"internal/cfg"`, `"github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"`,
    68  	)
    69  
    70  	htmlTemplateReplacers = strings.NewReplacer(
    71  		`. "html/template"`, `. "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"`,
    72  		`"html/template"`, `template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"`,
    73  		"\"text/template\"\n", "template \"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate\"\n",
    74  		`"html/template"`, `htmltemplate "html/template"`,
    75  		`"fmt"`, `htmltemplate "html/template"`,
    76  		`t.Skip("this test currently fails with -race; see issue #39807")`, `// t.Skip("this test currently fails with -race; see issue #39807")`,
    77  	)
    78  )
    79  
    80  func commonReplace(name, content string) string {
    81  	if strings.HasSuffix(name, "_test.go") {
    82  		content = strings.Replace(content, "package template\n", `// +build go1.13,!windows
    83  
    84  package template
    85  `, 1)
    86  		content = strings.Replace(content, "package template_test\n", `// +build go1.13
    87  
    88  package template_test
    89  `, 1)
    90  
    91  		content = strings.Replace(content, "package parse\n", `// +build go1.13
    92  
    93  package parse
    94  `, 1)
    95  
    96  	}
    97  
    98  	return content
    99  }
   100  
   101  var goPackages = []goPackage{
   102  	{
   103  		srcPkg: "text/template", dstPkg: "texttemplate",
   104  		replacer: func(name, content string) string { return textTemplateReplacers.Replace(commonReplace(name, content)) },
   105  	},
   106  	{
   107  		srcPkg: "html/template", dstPkg: "htmltemplate", replacer: func(name, content string) string {
   108  			if strings.HasSuffix(name, "content.go") {
   109  				// Remove template.HTML types. We need to use the Go types.
   110  				content = removeAll(`(?s)// Strings of content.*?\)\n`, content)
   111  			}
   112  
   113  			content = commonReplace(name, content)
   114  
   115  			return htmlTemplateReplacers.Replace(content)
   116  		},
   117  		rewriter: func(name string) {
   118  			for _, s := range []string{"CSS", "HTML", "HTMLAttr", "JS", "JSStr", "URL", "Srcset"} {
   119  				rewrite(name, fmt.Sprintf("%s -> htmltemplate.%s", s, s))
   120  			}
   121  			rewrite(name, `"text/template/parse" -> "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"`)
   122  		},
   123  	},
   124  	{srcPkg: "internal/fmtsort", dstPkg: "fmtsort", rewriter: func(name string) {
   125  		rewrite(name, `"internal/fmtsort" -> "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"`)
   126  	}},
   127  	{
   128  		srcPkg: "internal/testenv", dstPkg: "testenv",
   129  		replacer: func(name, content string) string { return testEnvReplacers.Replace(content) }, rewriter: func(name string) {
   130  			rewrite(name, `"internal/testenv" -> "github.com/gohugoio/hugo/tpl/internal/go_templates/testenv"`)
   131  		},
   132  	},
   133  	{srcPkg: "internal/cfg", dstPkg: "cfg", rewriter: func(name string) {
   134  		rewrite(name, `"internal/cfg" -> "github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"`)
   135  	}},
   136  }
   137  
   138  var fs = afero.NewOsFs()
   139  
   140  // Removes all non-Hugo files in the go_templates folder.
   141  func cleanFork() {
   142  	must(filepath.Walk(filepath.Join(forkRoot), func(path string, info os.FileInfo, err error) error {
   143  		if !info.IsDir() && len(path) > 10 && !strings.Contains(path, "hugo") {
   144  			must(fs.Remove(path))
   145  		}
   146  		return nil
   147  	}))
   148  }
   149  
   150  func must(err error, what ...string) {
   151  	if err != nil {
   152  		log.Fatal(what, " ERROR: ", err)
   153  	}
   154  }
   155  
   156  func copyGoPackage(dst, src string) {
   157  	from := filepath.Join(goSource, src)
   158  	to := filepath.Join(forkRoot, dst)
   159  	fmt.Println("Copy", from, "to", to)
   160  	must(hugio.CopyDir(fs, from, to, func(s string) bool { return true }))
   161  }
   162  
   163  func doWithGoFiles(dir string,
   164  	rewrite func(name string),
   165  	transform func(name, in string) string) {
   166  	if rewrite == nil && transform == nil {
   167  		return
   168  	}
   169  	must(filepath.Walk(filepath.Join(forkRoot, dir), func(path string, info os.FileInfo, err error) error {
   170  		if info.IsDir() {
   171  			return nil
   172  		}
   173  
   174  		if !strings.HasSuffix(path, ".go") || strings.Contains(path, "hugo_") {
   175  			return nil
   176  		}
   177  
   178  		fmt.Println("Handle", path)
   179  
   180  		if rewrite != nil {
   181  			rewrite(path)
   182  		}
   183  
   184  		if transform == nil {
   185  			return nil
   186  		}
   187  
   188  		data, err := os.ReadFile(path)
   189  		must(err)
   190  		f, err := os.Create(path)
   191  		must(err)
   192  		defer f.Close()
   193  		_, err = f.WriteString(transform(path, string(data)))
   194  		must(err)
   195  
   196  		return nil
   197  	}))
   198  }
   199  
   200  func removeAll(expression, content string) string {
   201  	re := regexp.MustCompile(expression)
   202  	return re.ReplaceAllString(content, "")
   203  }
   204  
   205  func rewrite(filename, rule string) {
   206  	cmf, _ := hexec.SafeCommand("gofmt", "-w", "-r", rule, filename)
   207  	out, err := cmf.CombinedOutput()
   208  	if err != nil {
   209  		log.Fatal("gofmt failed:", string(out))
   210  	}
   211  }
   212  
   213  func goimports(dir string) {
   214  	cmf, _ := hexec.SafeCommand("goimports", "-w", dir)
   215  	out, err := cmf.CombinedOutput()
   216  	if err != nil {
   217  		log.Fatal("goimports failed:", string(out))
   218  	}
   219  }
   220  
   221  func gofmt(dir string) {
   222  	cmf, _ := hexec.SafeCommand("gofmt", "-w", dir)
   223  	out, err := cmf.CombinedOutput()
   224  	if err != nil {
   225  		log.Fatal("gofmt failed:", string(out))
   226  	}
   227  }