package function // Updating Templates: // See documentation in ./templates/README.md // go get github.com/markbates/pkger //go:generate pkger import ( "errors" "io" "os" "path/filepath" "sort" "strings" "github.com/markbates/pkger" ) // fileAccessor encapsulates methods for accessing template files. type fileAccessor interface { Stat(name string) (os.FileInfo, error) Open(p string) (file, error) } type file interface { Readdir(int) ([]os.FileInfo, error) Read([]byte) (int, error) Close() error } // When pkger is run, code analysis detects this Include statement, // triggering the serialization of the templates directory and all // its contents into pkged.go, which is then made available via // a pkger fileAccessor. // Path is relative to the go module root. func init() { _ = pkger.Include("/templates") } type templateWriter struct { // Extensible Template Repositories // templates on disk (extensible templates) // Stored on disk at path: // [customTemplatesPath]/[repository]/[runtime]/[template] // For example // ~/.config/func/boson/go/http" // Specified when writing templates as simply: // Write([runtime], [repository], [path]) // For example // w := templateWriter{templates:"/home/username/.config/func/templates") // w.Write("go", "boson/http") // Ie. "Using the custom templates in the func configuration directory, // write the Boson HTTP template for the Go runtime." templates string verbose bool } var ( ErrRepositoryNotFound = errors.New("repository not found") ErrRepositoriesNotDefined = errors.New("custom template repositories location not specified") ErrRuntimeNotFound = errors.New("runtime not found") ErrTemplateNotFound = errors.New("template not found") ErrTemplateMissingRepository = errors.New("template name missing repository prefix") ) func (t templateWriter) Write(runtime, template, dest string) error { if template == "" { template = DefaultTemplate } if isCustom(template) { return writeCustom(t.templates, runtime, template, dest) } return writeEmbedded(runtime, template, dest) } func isCustom(template string) bool { return len(strings.Split(template, "/")) > 1 } func writeCustom(templatesPath, runtime, templateFullName, dest string) error { if templatesPath == "" { return ErrRepositoriesNotDefined } if !repositoryExists(templatesPath, templateFullName) { return ErrRepositoryNotFound } // ensure that the templateFullName is of the format "repoName/templateName" cc := strings.Split(templateFullName, "/") if len(cc) != 2 { return ErrTemplateMissingRepository } repo := cc[0] template := cc[1] runtimePath := filepath.Join(templatesPath, repo, runtime) _, err := os.Stat(runtimePath) if err != nil { return ErrRuntimeNotFound } // Example FileSystem path: // /home/alice/.config/func/templates/boson/go/json templatePath := filepath.Join(templatesPath, repo, runtime, template) _, err = os.Stat(templatePath) if err != nil { return ErrTemplateNotFound } return copy(templatePath, dest, filesystemAccessor{}) } func writeEmbedded(runtime, template, dest string) (err error) { // Copy files to the destination // Example embedded path: // /templates/go/http runtimePath := filepath.Join("/templates", runtime) _, err = pkger.Stat(runtimePath) if err != nil { return ErrRuntimeNotFound } templatePath := filepath.Join("/templates", runtime, template) _, err = pkger.Stat(templatePath) if err != nil { return ErrTemplateNotFound } return copy(templatePath, dest, embeddedAccessor{}) } type embeddedAccessor struct{} func (a embeddedAccessor) Stat(path string) (os.FileInfo, error) { return pkger.Stat(path) } func (a embeddedAccessor) Open(path string) (file, error) { return pkger.Open(path) } type filesystemAccessor struct{} func (a filesystemAccessor) Stat(path string) (os.FileInfo, error) { return os.Stat(path) } func (a filesystemAccessor) Open(path string) (file, error) { return os.Open(path) } func repositoryExists(repositories, template string) bool { cc := strings.Split(template, "/") _, err := os.Stat(filepath.Join(repositories, cc[0])) return err == nil } func copy(src, dest string, accessor fileAccessor) (err error) { node, err := accessor.Stat(src) if err != nil { return } if node.IsDir() { return copyNode(src, dest, accessor) } else { return copyLeaf(src, dest, accessor) } } func copyNode(src, dest string, accessor fileAccessor) (err error) { node, err := accessor.Stat(src) if err != nil { return } err = os.MkdirAll(dest, node.Mode()) if err != nil { return } children, err := readDir(src, accessor) if err != nil { return } for _, child := range children { if err = copy(filepath.Join(src, child.Name()), filepath.Join(dest, child.Name()), accessor); err != nil { return } } return } func readDir(src string, accessor fileAccessor) ([]os.FileInfo, error) { f, err := accessor.Open(src) if err != nil { return nil, err } list, err := f.Readdir(-1) f.Close() if err != nil { return nil, err } sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() }) return list, nil } func copyLeaf(src, dest string, accessor fileAccessor) (err error) { srcFile, err := accessor.Open(src) if err != nil { return } defer srcFile.Close() srcFileInfo, err := accessor.Stat(src) if err != nil { return } destFile, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcFileInfo.Mode()) if err != nil { return } defer destFile.Close() _, err = io.Copy(destFile, srcFile) return }