Files

408 lines
11 KiB
Go
Raw Permalink Normal View History

2018-12-26 16:27:07 +11:00
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
2019-03-06 19:11:46 +11:00
"os"
2018-12-26 16:27:07 +11:00
"path/filepath"
2020-10-29 20:18:52 +11:00
"runtime"
2019-07-01 21:35:06 +10:00
"sort"
2018-12-26 16:27:07 +11:00
"strings"
2019-03-02 12:54:10 +11:00
"github.com/leaanthony/slicer"
2018-12-26 16:27:07 +11:00
)
2019-11-05 14:47:05 +11:00
// PackageManager indicates different package managers
type PackageManager int
const (
// UNKNOWN package manager
UNKNOWN PackageManager = iota
// NPM package manager
NPM
// YARN package manager
YARN
)
2018-12-26 16:27:07 +11:00
type author struct {
Name string `json:"name"`
Email string `json:"email"`
}
type frontend struct {
Dir string `json:"dir"`
Install string `json:"install"`
Build string `json:"build"`
2019-01-30 09:00:46 +11:00
Bridge string `json:"bridge"`
2019-01-31 18:59:07 +11:00
Serve string `json:"serve"`
2018-12-26 16:27:07 +11:00
}
type framework struct {
Name string `json:"name"`
BuildTag string `json:"buildtag"`
Options map[string]string `json:"options,omitempty"`
}
// ProjectHelper is a helper struct for managing projects
type ProjectHelper struct {
log *Logger
system *SystemHelper
templates *TemplateHelper
}
// NewProjectHelper creates a new Project helper struct
func NewProjectHelper() *ProjectHelper {
return &ProjectHelper{
log: NewLogger(),
system: NewSystemHelper(),
templates: NewTemplateHelper(),
}
}
// GenerateProject generates a new project using the options given
func (ph *ProjectHelper) GenerateProject(projectOptions *ProjectOptions) error {
// Calculate project path
projectPath, err := filepath.Abs(projectOptions.OutputDirectory)
if err != nil {
return err
}
2019-03-02 12:54:10 +11:00
_ = projectPath
2018-12-26 16:27:07 +11:00
if fs.DirExists(projectPath) {
return fmt.Errorf("directory '%s' already exists", projectPath)
}
// Create project directory
err = fs.MkDir(projectPath)
if err != nil {
return err
}
// Create and save project config
err = projectOptions.WriteProjectConfig()
if err != nil {
return err
}
err = ph.templates.InstallTemplate(projectPath, projectOptions)
if err != nil {
return err
}
2019-02-20 22:24:47 +11:00
// // If we are on windows, dump a windows_resource.json
// if runtime.GOOS == "windows" {
// ph.GenerateWindowsResourceConfig(projectOptions)
// }
2018-12-26 16:27:07 +11:00
return nil
}
2019-02-20 22:24:47 +11:00
// // GenerateWindowsResourceConfig generates the default windows resource file
// func (ph *ProjectHelper) GenerateWindowsResourceConfig(po *ProjectOptions) {
// fmt.Println(buffer.String())
// // vi.Build()
// // vi.Walk()
// // err := vi.WriteSyso(outPath, runtime.GOARCH)
// }
2018-12-26 16:27:07 +11:00
// LoadProjectConfig loads the project config from the given directory
func (ph *ProjectHelper) LoadProjectConfig(dir string) (*ProjectOptions, error) {
po := ph.NewProjectOptions()
err := po.LoadConfig(dir)
return po, err
}
// NewProjectOptions creates a new default set of project options
func (ph *ProjectHelper) NewProjectOptions() *ProjectOptions {
result := ProjectOptions{
2019-03-02 12:54:10 +11:00
Name: "",
Description: "Enter your project description",
Version: "0.1.0",
BinaryName: "",
2019-10-08 06:12:08 +11:00
system: ph.system,
log: ph.log,
templates: ph.templates,
2019-03-02 12:54:10 +11:00
Author: &author{},
2018-12-26 16:27:07 +11:00
}
// Populate system config
config, err := ph.system.LoadConfig()
if err == nil {
result.Author.Name = config.Name
result.Author.Email = config.Email
}
return &result
}
// ProjectOptions holds all the options available for a project
type ProjectOptions struct {
Name string `json:"name"`
Description string `json:"description"`
Author *author `json:"author,omitempty"`
Version string `json:"version"`
OutputDirectory string `json:"-"`
UseDefaults bool `json:"-"`
Template string `json:"-"`
BinaryName string `json:"binaryname"`
FrontEnd *frontend `json:"frontend,omitempty"`
2020-10-29 20:18:52 +11:00
Tags string `json:"tags"`
NPMProjectName string `json:"-"`
system *SystemHelper
log *Logger
templates *TemplateHelper
selectedTemplate *TemplateDetails
WailsVersion string
typescriptDefsFilename string
2020-02-23 06:03:00 +11:00
Verbose bool `json:"-"`
2020-02-19 13:42:09 +01:00
CrossCompile bool
2020-02-18 20:26:26 +01:00
Platform string
2020-02-19 13:42:09 +01:00
Architecture string
2020-06-22 20:11:56 +10:00
LdFlags string
2020-10-29 20:18:52 +11:00
GoPath string
UseFirebug bool
// Supported platforms
Platforms []string `json:"platforms,omitempty"`
}
// PlatformSupported returns true if the template is supported
// on the current platform
func (po *ProjectOptions) PlatformSupported() bool {
// Default is all platforms supported
if len(po.Platforms) == 0 {
return true
}
// Check that the platform is in the list
platformsSupported := slicer.String(po.Platforms)
return platformsSupported.Contains(runtime.GOOS)
2018-12-26 16:27:07 +11:00
}
// Defaults sets the default project template
func (po *ProjectOptions) Defaults() {
2019-03-02 12:54:10 +11:00
po.Template = "vuebasic"
2019-06-26 19:46:51 +10:00
po.WailsVersion = Version
2018-12-26 16:27:07 +11:00
}
// SetTypescriptDefsFilename indicates that we want to generate typescript bindings to the given file
func (po *ProjectOptions) SetTypescriptDefsFilename(filename string) {
po.typescriptDefsFilename = filename
}
2019-11-05 14:47:05 +11:00
// GetNPMBinaryName returns the type of package manager used by the project
func (po *ProjectOptions) GetNPMBinaryName() (PackageManager, error) {
if po.FrontEnd == nil {
return UNKNOWN, fmt.Errorf("No frontend specified in project options")
}
if strings.Index(po.FrontEnd.Install, "npm") > -1 {
return NPM, nil
}
if strings.Index(po.FrontEnd.Install, "yarn") > -1 {
return YARN, nil
}
2020-04-18 15:01:35 -05:00
2019-11-05 14:47:05 +11:00
return UNKNOWN, nil
}
2018-12-26 16:27:07 +11:00
// PromptForInputs asks the user to input project details
func (po *ProjectOptions) PromptForInputs() error {
2019-02-19 19:25:33 +11:00
processProjectName(po)
2018-12-26 16:27:07 +11:00
2019-02-19 19:25:33 +11:00
processBinaryName(po)
2018-12-26 16:27:07 +11:00
2019-02-19 19:25:33 +11:00
err := processOutputDirectory(po)
2019-02-05 08:43:50 +11:00
if err != nil {
return err
2018-12-26 16:27:07 +11:00
}
2019-03-02 12:54:10 +11:00
// Process Templates
templateList := slicer.Interface()
options := slicer.String()
2019-05-18 07:15:15 +10:00
templateDetails, err := po.templates.GetTemplateDetails()
if err != nil {
return err
2018-12-26 16:27:07 +11:00
}
2019-05-18 07:15:15 +10:00
if po.Template != "" {
// Check template is valid if given
if templateDetails[po.Template] == nil {
keys := make([]string, 0, len(templateDetails))
for k := range templateDetails {
keys = append(keys, k)
}
return fmt.Errorf("invalid template name '%s'. Valid options: %s", po.Template, strings.Join(keys, ", "))
}
po.selectedTemplate = templateDetails[po.Template]
} else {
2019-03-02 12:54:10 +11:00
2019-07-01 21:35:06 +10:00
keys := make([]string, 0)
for k := range templateDetails {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
templateDetail := templateDetails[k]
2019-05-18 07:15:15 +10:00
templateList.Add(templateDetail)
2020-10-29 20:18:52 +11:00
if !templateDetail.Metadata.PlatformSupported() {
templateDetail.Metadata.Name = "* " + templateDetail.Metadata.Name
}
2019-05-18 07:15:15 +10:00
options.Add(fmt.Sprintf("%s - %s", templateDetail.Metadata.Name, templateDetail.Metadata.ShortDescription))
}
templateIndex := 0
if len(options.AsSlice()) > 1 {
2020-10-29 20:18:52 +11:00
templateIndex = PromptSelection("Please select a template (* means unsupported on current platform)", options.AsSlice(), 0)
2019-05-18 07:15:15 +10:00
}
if len(templateList.AsSlice()) == 0 {
return fmt.Errorf("aborting: no templates found")
}
// After selection do this....
po.selectedTemplate = templateList.AsSlice()[templateIndex].(*TemplateDetails)
2018-12-26 16:27:07 +11:00
}
2020-10-29 20:18:52 +11:00
po.selectedTemplate.Metadata.Name = strings.TrimPrefix(po.selectedTemplate.Metadata.Name, "* ")
if !po.selectedTemplate.Metadata.PlatformSupported() {
println("WARNING: This template is unsupported on this platform!")
}
2019-05-18 07:34:39 +10:00
fmt.Println("Template: " + po.selectedTemplate.Metadata.Name)
2018-12-26 16:27:07 +11:00
// Setup NPM Project name
2019-01-13 18:02:51 +11:00
po.NPMProjectName = strings.ToLower(strings.Replace(po.Name, " ", "_", -1))
2018-12-26 16:27:07 +11:00
// Fix template name
2019-03-06 19:11:46 +11:00
po.Template = strings.Split(po.selectedTemplate.Path, string(os.PathSeparator))[0]
2018-12-26 16:27:07 +11:00
2019-03-02 12:54:10 +11:00
// // Populate template details
templateMetadata := po.selectedTemplate.Metadata
2019-01-31 18:59:07 +11:00
2019-02-05 18:51:08 +11:00
err = processTemplateMetadata(templateMetadata, po)
if err != nil {
return err
2019-01-31 18:59:07 +11:00
}
2018-12-26 16:27:07 +11:00
return nil
}
2018-12-26 19:09:18 +11:00
// WriteProjectConfig writes the project configuration into
// the project directory
2018-12-26 16:27:07 +11:00
func (po *ProjectOptions) WriteProjectConfig() error {
targetDir, err := filepath.Abs(po.OutputDirectory)
if err != nil {
return err
}
targetFile := filepath.Join(targetDir, "project.json")
filedata, err := json.MarshalIndent(po, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(targetFile, filedata, 0600)
}
2018-12-26 19:09:18 +11:00
// LoadConfig loads the project configuration file from the
// given directory
2018-12-26 16:27:07 +11:00
func (po *ProjectOptions) LoadConfig(projectDir string) error {
targetFile := filepath.Join(projectDir, "project.json")
rawBytes, err := ioutil.ReadFile(targetFile)
if err != nil {
return err
}
return json.Unmarshal(rawBytes, po)
}
2019-02-05 08:43:50 +11:00
func computeBinaryName(projectName string) string {
if projectName == "" {
return ""
}
var binaryNameComputed = strings.ToLower(projectName)
binaryNameComputed = strings.Replace(binaryNameComputed, " ", "-", -1)
binaryNameComputed = strings.Replace(binaryNameComputed, string(filepath.Separator), "-", -1)
binaryNameComputed = strings.Replace(binaryNameComputed, ":", "-", -1)
return binaryNameComputed
}
2019-02-19 19:25:33 +11:00
func processOutputDirectory(po *ProjectOptions) error {
// po.OutputDirectory
2019-02-20 22:24:47 +11:00
if po.OutputDirectory == "" {
2019-02-22 09:03:41 +11:00
po.OutputDirectory = PromptRequired("Project directory name", computeBinaryName(po.Name))
2019-02-05 08:43:50 +11:00
}
2019-02-20 22:24:47 +11:00
projectPath, err := filepath.Abs(po.OutputDirectory)
if err != nil {
return err
}
if NewFSHelper().DirExists(projectPath) {
return fmt.Errorf("directory '%s' already exists", projectPath)
}
fmt.Println("Project Directory: " + po.OutputDirectory)
2019-02-05 08:43:50 +11:00
return nil
}
2019-02-05 18:51:08 +11:00
2019-02-19 19:25:33 +11:00
func processProjectName(po *ProjectOptions) {
if po.Name == "" {
po.Name = Prompt("The name of the project", "My Project")
2019-02-05 18:51:08 +11:00
}
2019-02-20 22:24:47 +11:00
fmt.Println("Project Name: " + po.Name)
2019-02-05 18:51:08 +11:00
}
2019-02-19 19:25:33 +11:00
func processBinaryName(po *ProjectOptions) {
if po.BinaryName == "" {
var binaryNameComputed = computeBinaryName(po.Name)
po.BinaryName = Prompt("The output binary name", binaryNameComputed)
2019-02-05 18:51:08 +11:00
}
2019-02-20 22:24:47 +11:00
fmt.Println("Output binary Name: " + po.BinaryName)
2019-02-05 18:51:08 +11:00
}
2019-03-02 12:54:10 +11:00
func processTemplateMetadata(templateMetadata *TemplateMetadata, po *ProjectOptions) error {
if templateMetadata.FrontendDir != "" {
2019-02-05 18:51:08 +11:00
po.FrontEnd = &frontend{}
2019-03-02 12:54:10 +11:00
po.FrontEnd.Dir = templateMetadata.FrontendDir
2019-02-05 18:51:08 +11:00
}
2019-03-02 12:54:10 +11:00
if templateMetadata.Install != "" {
2019-02-05 18:51:08 +11:00
if po.FrontEnd == nil {
return fmt.Errorf("install set in template metadata but not frontenddir")
}
2019-03-02 12:54:10 +11:00
po.FrontEnd.Install = templateMetadata.Install
2019-02-05 18:51:08 +11:00
}
2019-03-02 12:54:10 +11:00
if templateMetadata.Build != "" {
2019-02-05 18:51:08 +11:00
if po.FrontEnd == nil {
return fmt.Errorf("build set in template metadata but not frontenddir")
}
2019-03-02 12:54:10 +11:00
po.FrontEnd.Build = templateMetadata.Build
2019-02-05 18:51:08 +11:00
}
2019-03-02 12:54:10 +11:00
if templateMetadata.Bridge != "" {
2019-02-05 18:51:08 +11:00
if po.FrontEnd == nil {
return fmt.Errorf("bridge set in template metadata but not frontenddir")
}
2019-03-02 12:54:10 +11:00
po.FrontEnd.Bridge = templateMetadata.Bridge
2019-02-05 18:51:08 +11:00
}
2019-03-02 12:54:10 +11:00
if templateMetadata.Serve != "" {
2019-02-05 18:51:08 +11:00
if po.FrontEnd == nil {
return fmt.Errorf("serve set in template metadata but not frontenddir")
}
2019-03-02 12:54:10 +11:00
po.FrontEnd.Serve = templateMetadata.Serve
2019-02-05 18:51:08 +11:00
}
2020-10-29 20:18:52 +11:00
// Save platforms
po.Platforms = templateMetadata.Platforms
2019-02-05 18:51:08 +11:00
return nil
}