267 lines
5.5 KiB
Go
267 lines
5.5 KiB
Go
|
package templatedir
|
||
|
|
||
|
import (
|
||
|
"io"
|
||
|
"io/fs"
|
||
|
"log"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"syscall"
|
||
|
"text/template"
|
||
|
)
|
||
|
|
||
|
// TemplateDir defines a directory containing directories and files, including template files
|
||
|
type TemplateDir struct {
|
||
|
fs fs.FS
|
||
|
templateFilters []string
|
||
|
dirs []string
|
||
|
standardFiles []string
|
||
|
templateFiles []string
|
||
|
ignoredFiles map[string]struct{}
|
||
|
renameFiles map[string]string
|
||
|
}
|
||
|
|
||
|
// New attempts to create a new TemplateDir from the given FS
|
||
|
func New(fs fs.FS) *TemplateDir {
|
||
|
|
||
|
return &TemplateDir{
|
||
|
fs: fs,
|
||
|
templateFilters: []string{".tmpl"},
|
||
|
ignoredFiles: make(map[string]struct{}),
|
||
|
renameFiles: make(map[string]string),
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// IgnoreFile will add the given filename to the list of files to ignore
|
||
|
// during extraction
|
||
|
func (t *TemplateDir) IgnoreFile(filename string) {
|
||
|
t.ignoredFiles[filename] = struct{}{}
|
||
|
}
|
||
|
|
||
|
// SetTemplateFilters sets the template filter. Each filename is checked to see if
|
||
|
// it contains this string and if so, it is deemed to be a template file
|
||
|
func (t *TemplateDir) SetTemplateFilters(filters []string) {
|
||
|
t.templateFilters = filters
|
||
|
}
|
||
|
|
||
|
func (t *TemplateDir) RenameFiles(renameFiles map[string]string) {
|
||
|
t.renameFiles = renameFiles
|
||
|
}
|
||
|
|
||
|
// Extract the templates to the given directory, using data as input
|
||
|
func (t *TemplateDir) Extract(targetDirectory string, data interface{}) error {
|
||
|
|
||
|
// Get the absolute path
|
||
|
targetDirectory, err := filepath.Abs(targetDirectory)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// If the targetDirectory doesn't exist, then create it
|
||
|
if _, err := os.Stat(targetDirectory); os.IsNotExist(err) == true {
|
||
|
// Create the targetDirectory
|
||
|
err = os.MkdirAll(targetDirectory, 0755)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Process the template files
|
||
|
err = t.processTemplateDirFiles(targetDirectory, data)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TemplateDir) processTemplateDirFiles(targetDirectory string, data interface{}) error {
|
||
|
// Categorise all files
|
||
|
err := t.categoriseFiles()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Create all directories
|
||
|
err = t.createDirectories(targetDirectory)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Process TemplateDirs
|
||
|
err = t.processTemplateDirs(targetDirectory, data)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Copy files
|
||
|
err = t.copyFiles(targetDirectory)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TemplateDir) categoriseFiles() error {
|
||
|
return fs.WalkDir(t.fs, ".", t.categoriseFile)
|
||
|
}
|
||
|
|
||
|
func (t *TemplateDir) categoriseFile(path string, info fs.DirEntry, err error) error {
|
||
|
|
||
|
// Process error
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Is it a directory?
|
||
|
if info.IsDir() {
|
||
|
// Ignore base dir
|
||
|
if path != "." {
|
||
|
t.dirs = append(t.dirs, path)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Get the filename
|
||
|
filename := filepath.Base(path)
|
||
|
|
||
|
// Is it a file we are ignoring?
|
||
|
_, ignored := t.ignoredFiles[filename]
|
||
|
if ignored {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Is it a template?
|
||
|
for _, filter := range t.templateFilters {
|
||
|
if strings.Index(filename, filter) > -1 {
|
||
|
t.templateFiles = append(t.templateFiles, path)
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Treat as standard file
|
||
|
t.standardFiles = append(t.standardFiles, path)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TemplateDir) convertPathTarget(path string, targetDirectory string) string {
|
||
|
return filepath.Join(targetDirectory, path)
|
||
|
}
|
||
|
|
||
|
func (t *TemplateDir) createDirectories(targetDirectory string) error {
|
||
|
|
||
|
// Iterate all directories and attempt to create them
|
||
|
for _, dirPath := range t.dirs {
|
||
|
|
||
|
targetDir := t.convertPathTarget(dirPath, targetDirectory)
|
||
|
|
||
|
// Create the directory
|
||
|
err := os.MkdirAll(targetDir, 0755)
|
||
|
|
||
|
// Ignore directory exists errors
|
||
|
if err != nil && err != syscall.EEXIST {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TemplateDir) processTemplateDirs(targetDirectory string, data interface{}) error {
|
||
|
|
||
|
// Iterate template files
|
||
|
for _, templateFile := range t.templateFiles {
|
||
|
|
||
|
// Parse template
|
||
|
tmpl, err := template.ParseFS(t.fs, templateFile)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Convert path to target path
|
||
|
targetFile := t.convertPathTarget(templateFile, targetDirectory)
|
||
|
|
||
|
// update filename
|
||
|
baseDir := filepath.Dir(targetFile)
|
||
|
filename := filepath.Base(targetFile)
|
||
|
for _, filter := range t.templateFilters {
|
||
|
filename = strings.ReplaceAll(filename, filter, "")
|
||
|
}
|
||
|
renamedFile := t.renameFiles[filename]
|
||
|
if renamedFile != "" {
|
||
|
filename = renamedFile
|
||
|
}
|
||
|
targetFile = filepath.Join(baseDir, filename)
|
||
|
|
||
|
// Create target file
|
||
|
writer, err := os.Create(targetFile)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = tmpl.Execute(writer, data)
|
||
|
if err != nil {
|
||
|
err2 := writer.Close()
|
||
|
if err2 != nil {
|
||
|
return err2
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = writer.Close()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TemplateDir) copyFiles(targetDirectory string) error {
|
||
|
|
||
|
// Iterate over files
|
||
|
for _, filename := range t.standardFiles {
|
||
|
targetFile := filename
|
||
|
renamedFile := t.renameFiles[filename]
|
||
|
if renamedFile != "" {
|
||
|
targetFile = renamedFile
|
||
|
}
|
||
|
|
||
|
targetFilename := t.convertPathTarget(targetFile, targetDirectory)
|
||
|
err := t.copyFile(filename, targetFilename)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TemplateDir) copyFile(source, target string) error {
|
||
|
s, err := t.fs.Open(source)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer func(s fs.File) {
|
||
|
err := s.Close()
|
||
|
if err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
}(s)
|
||
|
d, err := os.Create(target)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if _, err := io.Copy(d, s); err != nil {
|
||
|
err := d.Close()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
return d.Close()
|
||
|
}
|