5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 07:12:01 +08:00
wails/v2/internal/templates/templates.go
2020-11-21 11:56:01 +11:00

322 lines
7.4 KiB
Go

package templates
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/leaanthony/gosod"
"github.com/leaanthony/slicer"
"github.com/olekukonko/tablewriter"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// Cahce for the templates
// We use this because we need different views of the same data
var templateCache []Template = nil
// Data contains the data we wish to embed during template installation
type Data struct {
ProjectName string
BinaryName string
WailsVersion string
NPMProjectName string
AuthorName string
AuthorEmail string
AuthorNameAndEmail string
WailsDirectory string
}
// Options for installing a template
type Options struct {
ProjectName string
TemplateName string
BinaryName string
TargetDir string
Logger *clilogger.CLILogger
GenerateVSCode bool
PathToDesktopBinary string
PathToServerBinary string
InitGit bool
AuthorName string
AuthorEmail string
}
// Template holds data relating to a template
// including the metadata stored in template.json
type Template struct {
// Template details
Name string `json:"name"`
ShortName string `json:"shortname"`
Author string `json:"author"`
Description string `json:"description"`
HelpURL string `json:"helpurl"`
// Other data
Directory string `json:"-"`
}
func parseTemplate(directory string) (Template, error) {
templateJSON := filepath.Join(directory, "template.json")
var result Template
data, err := ioutil.ReadFile(templateJSON)
if err != nil {
return result, err
}
result.Directory = directory
err = json.Unmarshal(data, &result)
return result, err
}
// TemplateShortNames returns a slicer of short template names
func TemplateShortNames() (*slicer.StringSlicer, error) {
var result slicer.StringSlicer
// If the cache isn't loaded, load it
if templateCache == nil {
err := loadTemplateCache()
if err != nil {
return nil, err
}
}
for _, template := range templateCache {
result.Add(template.ShortName)
}
return &result, nil
}
// List returns the list of available templates
func List() ([]Template, error) {
// If the cache isn't loaded, load it
if templateCache == nil {
err := loadTemplateCache()
if err != nil {
return nil, err
}
}
return templateCache, nil
}
// getTemplateByShortname returns the template with the given short name
func getTemplateByShortname(shortname string) (Template, error) {
var result Template
// If the cache isn't loaded, load it
if templateCache == nil {
err := loadTemplateCache()
if err != nil {
return result, err
}
}
for _, template := range templateCache {
if template.ShortName == shortname {
return template, nil
}
}
return result, fmt.Errorf("shortname '%s' is not a valid template shortname", shortname)
}
// Loads the template cache
func loadTemplateCache() error {
// Get local template directory
templateDir := fs.RelativePath("templates")
// Get directories
files, err := ioutil.ReadDir(templateDir)
if err != nil {
return err
}
// Reset cache
templateCache = []Template{}
for _, file := range files {
if file.IsDir() {
templateDir := filepath.Join(templateDir, file.Name())
template, err := parseTemplate(templateDir)
if err != nil {
// Cannot parse this template, continue
continue
}
templateCache = append(templateCache, template)
}
}
return nil
}
// Install the given template
func Install(options *Options) error {
// Get cwd
cwd, err := os.Getwd()
if err != nil {
return err
}
// Did the user want to install in current directory?
if options.TargetDir == "" {
// If the current directory is empty, use it
isEmpty, err := fs.DirIsEmpty(cwd)
if err != nil {
return err
}
if isEmpty {
// Yes - use cwd
options.TargetDir = cwd
} else {
options.TargetDir = filepath.Join(cwd, options.ProjectName)
if fs.DirExists(options.TargetDir) {
return fmt.Errorf("cannot create project directory. Dir exists: %s", options.TargetDir)
}
}
} else {
// Get the absolute path of the given directory
targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir))
if err != nil {
return err
}
options.TargetDir = targetDir
if !fs.DirExists(options.TargetDir) {
err := fs.Mkdir(options.TargetDir)
if err != nil {
return err
}
}
}
// Get template
template, err := getTemplateByShortname(options.TemplateName)
if err != nil {
return err
}
// Use Gosod to install the template
installer, err := gosod.TemplateDir(template.Directory)
if err != nil {
return err
}
// Ignore template.json files
installer.IgnoreFilename("template.json")
// Setup the data.
// We use the directory name for the binary name, like Go
BinaryName := filepath.Base(options.TargetDir)
NPMProjectName := strings.ToLower(strings.ReplaceAll(BinaryName, " ", ""))
localWailsDirectory := fs.RelativePath("../..")
templateData := &Data{
ProjectName: options.ProjectName,
BinaryName: filepath.Base(options.TargetDir),
NPMProjectName: NPMProjectName,
WailsDirectory: localWailsDirectory,
AuthorEmail: options.AuthorEmail,
AuthorName: options.AuthorName,
}
// Create a formatted name and email combo.
if options.AuthorName != "" {
templateData.AuthorNameAndEmail = options.AuthorName + " "
}
if options.AuthorEmail != "" {
templateData.AuthorNameAndEmail += "<" + options.AuthorEmail + ">"
}
templateData.AuthorNameAndEmail = strings.TrimSpace(templateData.AuthorNameAndEmail)
// Extract the template
err = installer.Extract(options.TargetDir, templateData)
if err != nil {
return err
}
err = generateIDEFiles(options)
if err != nil {
return err
}
return nil
}
// OutputList prints the list of available tempaltes to the given logger
func OutputList(logger *clilogger.CLILogger) error {
templates, err := List()
if err != nil {
return err
}
table := tablewriter.NewWriter(logger.Writer)
table.SetHeader([]string{"Template", "Short Name", "Description"})
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding("\t") // pad with tabs
table.SetNoWhiteSpace(true)
for _, template := range templates {
table.Append([]string{template.Name, template.ShortName, template.Description})
}
table.Render()
return nil
}
func generateIDEFiles(options *Options) error {
if options.GenerateVSCode {
return generateVSCodeFiles(options)
}
return nil
}
func generateVSCodeFiles(options *Options) error {
targetDir := filepath.Join(options.TargetDir, ".vscode")
sourceDir := fs.RelativePath(filepath.Join("./ides/vscode"))
// Use Gosod to install the template
installer, err := gosod.TemplateDir(sourceDir)
if err != nil {
return err
}
binaryName := filepath.Base(options.TargetDir)
if runtime.GOOS == "windows" {
// yay windows
binaryName += ".exe"
}
options.PathToDesktopBinary = filepath.Join("build", runtime.GOOS, "desktop", binaryName)
options.PathToServerBinary = filepath.Join("build", runtime.GOOS, "server", binaryName)
err = installer.Extract(targetDir, options)
if err != nil {
return err
}
return nil
}