internal/tmplfunc: mv go.dev/cmd/internal/tmplfunc up a few levels

Make internal/tmplfunc available to the whole module,
as one more step toward merging internal/web and go.dev/cmd/internal/site.

Also bring in rsc.io/tmplfunc support for fs.FS.

Change-Id: I65486c32136ec500de172b8297d0605fef9d5c83
Reviewed-on: https://go-review.googlesource.com/c/website/+/339401
Trust: Russ Cox <rsc@golang.org>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/go.dev/cmd/internal/README.md b/go.dev/cmd/internal/README.md
deleted file mode 100644
index 9530049..0000000
--- a/go.dev/cmd/internal/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-internal/fmtsort, internal/html/template, internal/text/template,
-and internal/text/template/parse are copied from the standard library
-as of May 2021. The text/template code contains various bug fixes
-that the site depends on, as well as two features planned for Go 1.18:
-break and continue in range loops and short-circuit and/or
-(CL 321491 and CL 321490).
-
-internal/tmplfunc is a copy of rsc.io/tmplfunc, which may some day
-make it into the standard library.
diff --git a/go.dev/cmd/internal/site/page.go b/go.dev/cmd/internal/site/page.go
index 154dd05..dea5439 100644
--- a/go.dev/cmd/internal/site/page.go
+++ b/go.dev/cmd/internal/site/page.go
@@ -14,7 +14,7 @@
 	"strings"
 	"time"
 
-	"golang.org/x/website/go.dev/cmd/internal/tmplfunc"
+	"golang.org/x/website/internal/tmplfunc"
 	"gopkg.in/yaml.v3"
 )
 
diff --git a/go.dev/cmd/internal/site/tmpl.go b/go.dev/cmd/internal/site/tmpl.go
index 9bf04c1..848cdae 100644
--- a/go.dev/cmd/internal/site/tmpl.go
+++ b/go.dev/cmd/internal/site/tmpl.go
@@ -9,8 +9,8 @@
 	"reflect"
 	"strings"
 
-	"golang.org/x/website/go.dev/cmd/internal/tmplfunc"
 	"golang.org/x/website/internal/backport/html/template"
+	"golang.org/x/website/internal/tmplfunc"
 	"gopkg.in/yaml.v3"
 )
 
diff --git a/internal/tmplfunc/README.md b/internal/tmplfunc/README.md
new file mode 100644
index 0000000..8e3d917
--- /dev/null
+++ b/internal/tmplfunc/README.md
@@ -0,0 +1,8 @@
+[![Documentation](https://pkg.go.dev/badge/rsc.io/tmplfunc.svg)](https://pkg.go.dev/rsc.io/tmplfunc)
+
+Package tmplfunc provides an extension of Go templates
+in which templates can be invoked as if they were functions.
+
+See the [package documentation](https://pkg.go.dev/rsc.io/tmplfunc) for details.
+
+
diff --git a/go.dev/cmd/internal/tmplfunc/func.go b/internal/tmplfunc/func.go
similarity index 95%
rename from go.dev/cmd/internal/tmplfunc/func.go
rename to internal/tmplfunc/func.go
index 7d20d9b..7e48e25 100644
--- a/go.dev/cmd/internal/tmplfunc/func.go
+++ b/internal/tmplfunc/func.go
@@ -63,6 +63,9 @@
 	return nil
 }
 
+// Funcs installs functions for all the templates in the set containing t.
+// After using t.Clone it is necessary to call Funcs on the result to arrange
+// for the functions to invoke the cloned templates and not the originals.
 func Funcs(t Template) error {
 	funcs := make(map[string]interface{})
 	switch t := t.(type) {
diff --git a/go.dev/cmd/internal/tmplfunc/testdata/x.tmpl b/internal/tmplfunc/testdata/x.tmpl
similarity index 100%
rename from go.dev/cmd/internal/tmplfunc/testdata/x.tmpl
rename to internal/tmplfunc/testdata/x.tmpl
diff --git a/go.dev/cmd/internal/tmplfunc/testdata/y.tmpl b/internal/tmplfunc/testdata/y.tmpl
similarity index 100%
rename from go.dev/cmd/internal/tmplfunc/testdata/y.tmpl
rename to internal/tmplfunc/testdata/y.tmpl
diff --git a/go.dev/cmd/internal/tmplfunc/tmpl.go b/internal/tmplfunc/tmpl.go
similarity index 86%
rename from go.dev/cmd/internal/tmplfunc/tmpl.go
rename to internal/tmplfunc/tmpl.go
index 1f115f5..6719ea8 100644
--- a/go.dev/cmd/internal/tmplfunc/tmpl.go
+++ b/internal/tmplfunc/tmpl.go
@@ -82,8 +82,11 @@
 import (
 	"fmt"
 	"io/ioutil"
+	"path"
 	"path/filepath"
 
+	"golang.org/x/website/internal/backport/io/fs"
+
 	htmltemplate "golang.org/x/website/internal/backport/html/template"
 	texttemplate "golang.org/x/website/internal/backport/text/template"
 )
@@ -118,6 +121,30 @@
 	return parseFiles(t, readFileOS, filenames...)
 }
 
+// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs
+// instead of the host operating system's file system.
+// It accepts a list of glob patterns.
+// (Note that most file names serve as glob patterns matching only themselves.)
+// It of course adds functions for the parsed templates.
+func ParseFS(t Template, fs fs.FS, patterns ...string) error {
+	return parseFS(t, fs, patterns)
+}
+
+func parseFS(t Template, fsys fs.FS, patterns []string) error {
+	var filenames []string
+	for _, pattern := range patterns {
+		list, err := fs.Glob(fsys, pattern)
+		if err != nil {
+			return err
+		}
+		if len(list) == 0 {
+			return fmt.Errorf("template: pattern matches no files: %#q", pattern)
+		}
+		filenames = append(filenames, list...)
+	}
+	return parseFiles(t, readFileFS(fsys), filenames...)
+}
+
 // parseFiles is the helper for the method and function. If the argument
 // template is nil, it is created from the first file.
 func parseFiles(t Template, readFile func(string) (string, []byte, error), filenames ...string) error {
@@ -211,3 +238,11 @@
 	b, err = ioutil.ReadFile(file)
 	return
 }
+
+func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
+	return func(file string) (name string, b []byte, err error) {
+		name = path.Base(file)
+		b, err = fs.ReadFile(fsys, file)
+		return
+	}
+}
diff --git a/go.dev/cmd/internal/tmplfunc/tmplfunc_test.go b/internal/tmplfunc/tmplfunc_test.go
similarity index 100%
rename from go.dev/cmd/internal/tmplfunc/tmplfunc_test.go
rename to internal/tmplfunc/tmplfunc_test.go