mirror of
https://github.com/wavetermdev/wails.git
synced 2026-04-22 15:26:15 -07:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d5825d73d | |||
| c4a042cb1d | |||
| 205f9476fa | |||
| a1230fcbb6 | |||
| ba5c32a4a1 | |||
| 58eee64326 | |||
| 41d786a13c | |||
| a2b7906c89 | |||
| eeb6fa4677 | |||
| 6a36d75774 | |||
| 6078f3c780 | |||
| fc11197725 | |||
| 8c40b99194 | |||
| de53fc6510 | |||
| d4f4feb429 | |||
| 02973c49ff | |||
| 01dce9f139 | |||
| 6c0906e87d | |||
| d7cfc4c71a | |||
| fec3e7eac5 | |||
| 9f5e2c7dd4 | |||
| c5276cca6c | |||
| 74dbbbed8a | |||
| 629ac4b93c | |||
| 5ece7e84b3 | |||
| eaba857676 | |||
| db489a3cae | |||
| 4821ab8597 | |||
| 670b769f82 | |||
| eb53399824 | |||
| 24e4fbfb68 | |||
| 7f54ca4ac3 | |||
| b224803e4d | |||
| 28b2025aaa | |||
| 77e85705d1 | |||
| 9fd24595c7 | |||
| 69067e85f5 | |||
| c8db58e00e | |||
| e96e0e0999 | |||
| 2da21ec528 | |||
| cda0b40414 | |||
| 7f4229dd6b | |||
| 68651b77f4 | |||
| bf001f5ad2 | |||
| d1907b4ce5 | |||
| 56363d193d | |||
| 8553f43080 | |||
| 587681bb8d | |||
| afbf80ea4a | |||
| c180d7dccb | |||
| c20aabc8f8 |
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Description**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behaviour:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behaviour**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**System Details**
|
||||
Please paste the output of `wails report` here.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
+2
-1
@@ -15,4 +15,5 @@ examples/**/example*
|
||||
!examples/**/*.*
|
||||
cmd/wails/wails
|
||||
.DS_Store
|
||||
tmp
|
||||
tmp
|
||||
dist
|
||||
@@ -0,0 +1,26 @@
|
||||
# This is an example goreleaser.yaml file with some sane defaults.
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
before:
|
||||
hooks:
|
||||
- ./scripts/updateversion.sh
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
main: ./cmd/wails/main.go
|
||||
archive:
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 wailsapp
|
||||
Copyright (c) 2018-Present Lea Anthony
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1 +1,84 @@
|
||||
# Coming Soon
|
||||
<p align="center" style="text-align: center">
|
||||
<img src="https://github.com/wailsapp/docs/raw/master/.vuepress/public/media/logo_cropped.png" width="40%"><br/>
|
||||
</p>
|
||||
<p align="center">
|
||||
A framework for building desktop applications using Go & Web Technologies.<br/><br/>
|
||||
<a href="https://github.com/wailsapp/wails/blob/master/LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/wailsapp/wails"><img src="https://goreportcard.com/badge/github.com/wailsapp/wails"/></a>
|
||||
<a href="http://godoc.org/github.com/wailsapp/wails"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"/></a>
|
||||
<a href="https://www.codefactor.io/repository/github/wailsapp/wails"><img src="https://www.codefactor.io/repository/github/wailsapp/wails/badge" alt="CodeFactor" /></a>
|
||||
<a href="https://github.com/wailsapp/wails/issues"><img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat" alt="CodeFactor" /></a>
|
||||
</p>
|
||||
|
||||
The traditional method of providing web interfaces to Go programs is via a built-in web server. Wails offers a different approach: it provides the ability to wrap both Go code and a web frontend into a single binary. Tools are provided to make this easy for you by handling project creation, compilation and bundling. All you have to do is get creative!
|
||||
|
||||
## Features
|
||||
|
||||
- Use standard Go libraries/frameworks for the backend
|
||||
- Use any frontend technology to build your UI
|
||||
- Expose Go methods/functions to the frontend via a single bind command
|
||||
- Uses native rendering engines - no embedded browser
|
||||
- Shared events system
|
||||
- Native file dialogs
|
||||
- Powerful cli tool
|
||||
- Multiplatform
|
||||
|
||||
## Installation
|
||||
|
||||
Wails uses cgo to bind to the native rendering engines so a number of platform dependent libraries are needed as well as an installation of Go. The basic requirements are:
|
||||
|
||||
- Go 1.11 or above
|
||||
- npm
|
||||
|
||||
### MacOS
|
||||
|
||||
Make sure you have the xcode command line tools installed. This can be done by running:
|
||||
|
||||
`xcode-select --install`
|
||||
|
||||
### Linux
|
||||
|
||||
#### Ubuntu 18.04
|
||||
|
||||
`sudo apt install pkg-config build-essential libgtk-3-dev libwebkit2gtk-4.0-dev`
|
||||
|
||||
Note: If you have successfully installed these dependencies on a different flavour of Linux, please consider submitting a PR.
|
||||
|
||||
### Windows
|
||||
|
||||
Windows requires gcc and related tooling. The recommended download is from [http://tdm-gcc.tdragon.net/download](http://tdm-gcc.tdragon.net/download). Once this is installed, you are good to go.
|
||||
|
||||
## Installation
|
||||
|
||||
Installation is as simple as running the following command:
|
||||
|
||||
<pre style='color:white'>
|
||||
go get -u github.com/wailsapp/wails/cmd/wails
|
||||
</pre>
|
||||
|
||||
## Next Steps
|
||||
|
||||
It is recommended at this stage to read the comprehensive documentation at [https://wails.app](https://wails.app).
|
||||
|
||||
## FAQ
|
||||
|
||||
* Is this an alternative to Electron?
|
||||
|
||||
Depends on your requirements. It's designed to make it easy for Go programmers to make lightweight desktop applications or add a frontend to their existing applications. Whilst Wails does not currently offer hooks into native elements such as menus, this may change in the future.
|
||||
|
||||
* Who is this project aimed at?
|
||||
|
||||
Go programmers who want to bundle an HMTL frontend with their applications, without resorting to creating a server and opening a browser to view it.
|
||||
|
||||
## Shoulders of Giants
|
||||
|
||||
Without the following people, this project would never have existed:
|
||||
|
||||
* [Dustin Krysak](https://wiki.ubuntu.com/bashfulrobot) - His support and feedback has been immense. More patience than you can throw a stick at.
|
||||
* [Serge Zaitsev](https://github.com/zserge) - Creator of [Webview](https://github.com/zserge/webview) which Wails uses for the windowing.
|
||||
|
||||
Special Mentions:
|
||||
|
||||
* [Bill Kennedy](https://twitter.com/goinggodotnet) - Go guru, encourager and all-round nice guy, whose energy and inspiration powered me on when I had none left.
|
||||
* [Mark Bates](https://github.com/markbates) - Creator of [Packr](https://github.com/gobuffalo/packr), inspiration for packing strategies.
|
||||
|
||||
|
||||
+1
-4
@@ -1,8 +1,6 @@
|
||||
package wails
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/cmd"
|
||||
)
|
||||
|
||||
@@ -11,6 +9,7 @@ func (app *App) setupCli() *cmd.Cli {
|
||||
|
||||
// Create a new cli
|
||||
result := cmd.NewCli(app.config.Title, "Debug build")
|
||||
result.Version(cmd.Version)
|
||||
|
||||
// Setup cli to handle loglevel and headless flags
|
||||
result.
|
||||
@@ -21,8 +20,6 @@ func (app *App) setupCli() *cmd.Cli {
|
||||
// Banner
|
||||
result.PreRun(func(cli *cmd.Cli) error {
|
||||
log := cmd.NewLogger()
|
||||
log.PrintSmallBanner()
|
||||
fmt.Println()
|
||||
log.YellowUnderline(app.config.Title + " - Debug Build")
|
||||
return nil
|
||||
})
|
||||
|
||||
+6
-3
@@ -153,8 +153,11 @@ func (b *boundFunction) setInputValue(index int, typ reflect.Type, val interface
|
||||
}
|
||||
}()
|
||||
|
||||
// Do the conversion
|
||||
result = reflect.ValueOf(val).Convert(typ)
|
||||
|
||||
// Translate javascript null values
|
||||
if val == nil {
|
||||
result = reflect.Zero(typ)
|
||||
} else {
|
||||
result = reflect.ValueOf(val).Convert(typ)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
+1
-4
@@ -195,10 +195,7 @@ func (c *Command) Action(callback Action) *Command {
|
||||
// PrintHelp - Output the help text for this command
|
||||
func (c *Command) PrintHelp() {
|
||||
c.log.PrintBanner()
|
||||
versionString := c.AppVersion
|
||||
if versionString != "" {
|
||||
versionString = " " + versionString
|
||||
}
|
||||
|
||||
commandTitle := c.CommandPath
|
||||
if c.Shortdescription != "" {
|
||||
commandTitle += " - " + c.Shortdescription
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -41,6 +41,14 @@ func (fs *FSHelper) FileExists(path string) bool {
|
||||
return fi.Mode().IsRegular()
|
||||
}
|
||||
|
||||
// CreateFile creates a file at the given filename location with the contents
|
||||
// set to the given data. It will create intermediary directories if needed.
|
||||
func (fs *FSHelper) CreateFile(filename string, data []byte) error {
|
||||
// Ensure directory exists
|
||||
fs.MkDirs(filepath.Dir(filename))
|
||||
return ioutil.WriteFile(filename, data, 0644)
|
||||
}
|
||||
|
||||
// MkDirs creates the given nested directories.
|
||||
// Returns error on failure
|
||||
func (fs *FSHelper) MkDirs(fullPath string, mode ...os.FileMode) error {
|
||||
|
||||
+22
-6
@@ -5,15 +5,17 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
mewn "github.com/leaanthony/mewn"
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/leaanthony/spinner"
|
||||
)
|
||||
|
||||
var fs = NewFSHelper()
|
||||
|
||||
// ValidateFrontendConfig checks if the frontend config is valid
|
||||
func ValidateFrontendConfig(projectOptions *ProjectOptions) error {
|
||||
if projectOptions.FrontEnd.Dir == "" {
|
||||
@@ -75,7 +77,13 @@ func BuildApplication(binaryName string, forceRebuild bool, buildMode string, pa
|
||||
packSpinner.Start()
|
||||
|
||||
buildCommand := slicer.String()
|
||||
buildCommand.AddSlice([]string{"mewn", "build"})
|
||||
buildCommand.Add("mewn")
|
||||
|
||||
if buildMode == BuildModeBridge {
|
||||
// Ignore errors
|
||||
buildCommand.Add("-i")
|
||||
}
|
||||
buildCommand.Add("build")
|
||||
|
||||
if binaryName != "" {
|
||||
buildCommand.Add("-o")
|
||||
@@ -108,6 +116,14 @@ func BuildApplication(binaryName string, forceRebuild bool, buildMode string, pa
|
||||
}
|
||||
packSpinner.Success()
|
||||
|
||||
// packageApp
|
||||
if packageApp {
|
||||
err = PackageApplication(projectOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -172,7 +188,7 @@ func CheckWindres() (err error) {
|
||||
}
|
||||
programHelper := NewProgramHelper()
|
||||
if !programHelper.IsInstalled("windres") {
|
||||
return fmt.Errorf("windres not installed. It comes by default with mingw. Ensure you have installed mingw correctly.")
|
||||
return fmt.Errorf("windres not installed. It comes by default with mingw. Ensure you have installed mingw correctly")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -238,10 +254,10 @@ func InstallFrontendDeps(projectDir string, projectOptions *ProjectOptions, forc
|
||||
}
|
||||
|
||||
// Copy bridge to project
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
bridgeFileSource := filepath.Join(path.Dir(filename), "..", "..", "wailsruntimeassets", "bridge", bridgeFile)
|
||||
bridgeAssets := mewn.Group("../wailsruntimeassets/bridge/")
|
||||
bridgeFileData := bridgeAssets.Bytes(bridgeFile)
|
||||
bridgeFileTarget := filepath.Join(projectDir, projectOptions.FrontEnd.Dir, projectOptions.FrontEnd.Bridge, "wailsbridge.js")
|
||||
err = fs.CopyFile(bridgeFileSource, bridgeFileTarget)
|
||||
err = fs.CreateFile(bridgeFileTarget, bridgeFileData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-4
@@ -12,7 +12,7 @@ const (
|
||||
// Unknown is the catch-all distro
|
||||
Unknown LinuxDistribution = 0
|
||||
// Ubuntu distribution
|
||||
Ubuntu LinuxDistribution = 1
|
||||
Ubuntu LinuxDistribution = 1
|
||||
)
|
||||
|
||||
// DistroInfo contains all the information relating to a linux distribution
|
||||
@@ -67,13 +67,11 @@ func GetLinuxDistroInfo() *DistroInfo {
|
||||
|
||||
// DpkgInstalled uses dpkg to see if a package is installed
|
||||
func DpkgInstalled(packageName string) (bool, error) {
|
||||
result := false
|
||||
program := NewProgramHelper()
|
||||
dpkg := program.FindProgram("dpkg")
|
||||
if dpkg == nil {
|
||||
return false, fmt.Errorf("cannot check dependencies: dpkg not found")
|
||||
}
|
||||
_, _, exitCode, _ := dpkg.Run("-L", packageName)
|
||||
result = exitCode == 0
|
||||
return result, nil
|
||||
return exitCode == 0, nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ func NewLogger() *Logger {
|
||||
return &Logger{errorOnly: false}
|
||||
}
|
||||
|
||||
// SetErrorOnly ensures that only errors are logged out
|
||||
func (l *Logger) SetErrorOnly(errorOnly bool) {
|
||||
l.errorOnly = errorOnly
|
||||
}
|
||||
@@ -99,6 +100,7 @@ func (l *Logger) Error(format string, a ...interface{}) {
|
||||
color.New(color.FgHiRed).PrintfFunc()("Error: "+format+"\n", a...)
|
||||
}
|
||||
|
||||
// PrintSmallBanner prints a condensed banner
|
||||
func (l *Logger) PrintSmallBanner(message ...string) {
|
||||
yellow := color.New(color.FgYellow).SprintFunc()
|
||||
red := color.New(color.FgRed).SprintFunc()
|
||||
|
||||
+3
-6
@@ -72,7 +72,7 @@ func (b *PackageHelper) Package(po *ProjectOptions) error {
|
||||
case "darwin":
|
||||
// Check we have the exe
|
||||
if !b.fs.FileExists(po.BinaryName) {
|
||||
return fmt.Errorf("cannot bundle non-existant binary file '%s'. Please build with 'wails build' first", po.BinaryName)
|
||||
return fmt.Errorf("cannot bundle non-existent binary file '%s'. Please build with 'wails build' first", po.BinaryName)
|
||||
}
|
||||
return b.packageOSX(po)
|
||||
case "windows":
|
||||
@@ -150,6 +150,7 @@ func (b *PackageHelper) packageOSX(po *ProjectOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// PackageWindows packages the application for windows platforms
|
||||
func (b *PackageHelper) PackageWindows(po *ProjectOptions, cleanUp bool) error {
|
||||
basename := strings.TrimSuffix(po.BinaryName, ".exe")
|
||||
|
||||
@@ -254,9 +255,5 @@ func (b *PackageHelper) packageIconOSX(resourceDir string) error {
|
||||
|
||||
}
|
||||
defer dest.Close()
|
||||
if err := icns.Encode(dest, srcImg); err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
return nil
|
||||
return icns.Encode(dest, srcImg)
|
||||
}
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ func (p *Program) GetFullPathToBinary() (string, error) {
|
||||
}
|
||||
|
||||
// Run will execute the program with the given parameters
|
||||
// Returns stdout + stderr as strings and an error if one occured
|
||||
// Returns stdout + stderr as strings and an error if one occurred
|
||||
func (p *Program) Run(vars ...string) (stdout, stderr string, exitCode int, err error) {
|
||||
command, err := p.GetFullPathToBinary()
|
||||
if err != nil {
|
||||
|
||||
+58
-75
@@ -4,9 +4,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
type author struct {
|
||||
@@ -47,15 +50,11 @@ func NewProjectHelper() *ProjectHelper {
|
||||
// GenerateProject generates a new project using the options given
|
||||
func (ph *ProjectHelper) GenerateProject(projectOptions *ProjectOptions) error {
|
||||
|
||||
fs := NewFSHelper()
|
||||
exists, err := ph.templates.TemplateExists(projectOptions.Template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// exists := ph.templates.TemplateExists(projectOptions.Template)
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("template '%s' is invalid", projectOptions.Template)
|
||||
}
|
||||
// if !exists {
|
||||
// return fmt.Errorf("template '%s' is invalid", projectOptions.Template)
|
||||
// }
|
||||
|
||||
// Calculate project path
|
||||
projectPath, err := filepath.Abs(projectOptions.OutputDirectory)
|
||||
@@ -63,6 +62,8 @@ func (ph *ProjectHelper) GenerateProject(projectOptions *ProjectOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = projectPath
|
||||
|
||||
if fs.DirExists(projectPath) {
|
||||
return fmt.Errorf("directory '%s' already exists", projectPath)
|
||||
}
|
||||
@@ -114,15 +115,14 @@ func (ph *ProjectHelper) LoadProjectConfig(dir string) (*ProjectOptions, error)
|
||||
// NewProjectOptions creates a new default set of project options
|
||||
func (ph *ProjectHelper) NewProjectOptions() *ProjectOptions {
|
||||
result := ProjectOptions{
|
||||
Name: "",
|
||||
Description: "Enter your project description",
|
||||
Version: "0.1.0",
|
||||
BinaryName: "",
|
||||
system: NewSystemHelper(),
|
||||
log: NewLogger(),
|
||||
templates: NewTemplateHelper(),
|
||||
templateNameMap: make(map[string]string),
|
||||
Author: &author{},
|
||||
Name: "",
|
||||
Description: "Enter your project description",
|
||||
Version: "0.1.0",
|
||||
BinaryName: "",
|
||||
system: NewSystemHelper(),
|
||||
log: NewLogger(),
|
||||
templates: NewTemplateHelper(),
|
||||
Author: &author{},
|
||||
}
|
||||
|
||||
// Populate system config
|
||||
@@ -137,25 +137,25 @@ func (ph *ProjectHelper) NewProjectOptions() *ProjectOptions {
|
||||
|
||||
// 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"`
|
||||
NPMProjectName string `json:"-"`
|
||||
system *SystemHelper
|
||||
log *Logger
|
||||
templates *TemplateHelper
|
||||
templateNameMap map[string]string // Converts template prompt text to template name
|
||||
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"`
|
||||
NPMProjectName string `json:"-"`
|
||||
system *SystemHelper
|
||||
log *Logger
|
||||
templates *TemplateHelper
|
||||
selectedTemplate *TemplateDetails
|
||||
}
|
||||
|
||||
// Defaults sets the default project template
|
||||
func (po *ProjectOptions) Defaults() {
|
||||
po.Template = "basic"
|
||||
po.Template = "vuebasic"
|
||||
}
|
||||
|
||||
// PromptForInputs asks the user to input project details
|
||||
@@ -170,48 +170,31 @@ func (po *ProjectOptions) PromptForInputs() error {
|
||||
return err
|
||||
}
|
||||
|
||||
templateDetails, err := po.templates.GetTemplateDetails()
|
||||
if err != nil {
|
||||
return err
|
||||
// Process Templates
|
||||
templateList := slicer.Interface()
|
||||
options := slicer.String()
|
||||
for _, templateDetails := range po.templates.TemplateList.details {
|
||||
templateList.Add(templateDetails)
|
||||
options.Add(fmt.Sprintf("%s - %s", templateDetails.Metadata.Name, templateDetails.Metadata.ShortDescription))
|
||||
}
|
||||
|
||||
templates := []string{}
|
||||
// Add a Custom Template
|
||||
// templates = append(templates, "Custom - Choose your own CSS framework")
|
||||
for templateName, templateDetails := range templateDetails {
|
||||
templateText := templateName
|
||||
// Check if metadata json exists
|
||||
if templateDetails.Metadata != nil {
|
||||
shortdescription := templateDetails.Metadata["shortdescription"]
|
||||
if shortdescription != "" {
|
||||
templateText += " - " + shortdescription.(string)
|
||||
}
|
||||
}
|
||||
templates = append(templates, templateText)
|
||||
po.templateNameMap[templateText] = templateName
|
||||
templateIndex := 0
|
||||
|
||||
if len(options.AsSlice()) > 1 {
|
||||
templateIndex = PromptSelection("Please select a template", options.AsSlice(), 0)
|
||||
}
|
||||
|
||||
if po.Template != "" {
|
||||
if _, ok := templateDetails[po.Template]; !ok {
|
||||
po.log.Error("Template '%s' invalid.", po.Template)
|
||||
templateSelected := PromptSelection("Select template", templates)
|
||||
po.Template = templates[templateSelected]
|
||||
}
|
||||
} else {
|
||||
templateSelected := PromptSelection("Select template", templates)
|
||||
po.Template = templates[templateSelected]
|
||||
}
|
||||
// After selection do this....
|
||||
po.selectedTemplate = templateList.AsSlice()[templateIndex].(*TemplateDetails)
|
||||
|
||||
// Setup NPM Project name
|
||||
po.NPMProjectName = strings.ToLower(strings.Replace(po.Name, " ", "_", -1))
|
||||
|
||||
// Fix template name
|
||||
if po.templateNameMap[po.Template] != "" {
|
||||
po.Template = po.templateNameMap[po.Template]
|
||||
}
|
||||
po.Template = strings.Split(po.selectedTemplate.Path, string(os.PathSeparator))[0]
|
||||
|
||||
// Populate template details
|
||||
templateMetadata := templateDetails[po.Template].Metadata
|
||||
// // Populate template details
|
||||
templateMetadata := po.selectedTemplate.Metadata
|
||||
|
||||
err = processTemplateMetadata(templateMetadata, po)
|
||||
if err != nil {
|
||||
@@ -299,36 +282,36 @@ func processBinaryName(po *ProjectOptions) {
|
||||
fmt.Println("Output binary Name: " + po.BinaryName)
|
||||
}
|
||||
|
||||
func processTemplateMetadata(templateMetadata map[string]interface{}, po *ProjectOptions) error {
|
||||
if templateMetadata["frontenddir"] != nil {
|
||||
func processTemplateMetadata(templateMetadata *TemplateMetadata, po *ProjectOptions) error {
|
||||
if templateMetadata.FrontendDir != "" {
|
||||
po.FrontEnd = &frontend{}
|
||||
po.FrontEnd.Dir = templateMetadata["frontenddir"].(string)
|
||||
po.FrontEnd.Dir = templateMetadata.FrontendDir
|
||||
}
|
||||
if templateMetadata["install"] != nil {
|
||||
if templateMetadata.Install != "" {
|
||||
if po.FrontEnd == nil {
|
||||
return fmt.Errorf("install set in template metadata but not frontenddir")
|
||||
}
|
||||
po.FrontEnd.Install = templateMetadata["install"].(string)
|
||||
po.FrontEnd.Install = templateMetadata.Install
|
||||
}
|
||||
if templateMetadata["build"] != nil {
|
||||
if templateMetadata.Build != "" {
|
||||
if po.FrontEnd == nil {
|
||||
return fmt.Errorf("build set in template metadata but not frontenddir")
|
||||
}
|
||||
po.FrontEnd.Build = templateMetadata["build"].(string)
|
||||
po.FrontEnd.Build = templateMetadata.Build
|
||||
}
|
||||
|
||||
if templateMetadata["bridge"] != nil {
|
||||
if templateMetadata.Bridge != "" {
|
||||
if po.FrontEnd == nil {
|
||||
return fmt.Errorf("bridge set in template metadata but not frontenddir")
|
||||
}
|
||||
po.FrontEnd.Bridge = templateMetadata["bridge"].(string)
|
||||
po.FrontEnd.Bridge = templateMetadata.Bridge
|
||||
}
|
||||
|
||||
if templateMetadata["serve"] != nil {
|
||||
if templateMetadata.Serve != "" {
|
||||
if po.FrontEnd == nil {
|
||||
return fmt.Errorf("serve set in template metadata but not frontenddir")
|
||||
}
|
||||
po.FrontEnd.Serve = templateMetadata["serve"].(string)
|
||||
po.FrontEnd.Serve = templateMetadata.Serve
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+32
-11
@@ -1,25 +1,35 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Prompt asks the user for a value
|
||||
func Prompt(question string, defaultValue ...string) string {
|
||||
var answer string
|
||||
haveDefault := len(defaultValue) > 0 && defaultValue[0] != ""
|
||||
|
||||
if haveDefault {
|
||||
question = fmt.Sprintf("%s (%s)", question, defaultValue[0])
|
||||
if len(defaultValue) > 0 {
|
||||
answer = defaultValue[0]
|
||||
question = fmt.Sprintf("%s (%s)", question, answer)
|
||||
}
|
||||
fmt.Printf(question + ": ")
|
||||
fmt.Scanln(&answer)
|
||||
if haveDefault {
|
||||
if len(answer) == 0 {
|
||||
answer = defaultValue[0]
|
||||
}
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
input, _ := reader.ReadString('\n')
|
||||
EOL := "\n"
|
||||
if runtime.GOOS == "windows" {
|
||||
EOL = "\r\n"
|
||||
}
|
||||
input = strings.Replace(input, EOL, "", -1)
|
||||
|
||||
if input != "" {
|
||||
answer = input
|
||||
}
|
||||
|
||||
return answer
|
||||
}
|
||||
|
||||
@@ -34,18 +44,29 @@ func PromptRequired(question string, defaultValue ...string) string {
|
||||
}
|
||||
|
||||
// PromptSelection asks the user to choose an option
|
||||
func PromptSelection(question string, options []string) int {
|
||||
func PromptSelection(question string, options []string, optionalDefaultValue ...int) int {
|
||||
|
||||
defaultValue := -1
|
||||
message := "Please choose an option"
|
||||
fmt.Println(question + ":")
|
||||
|
||||
if len(optionalDefaultValue) > 0 {
|
||||
defaultValue = optionalDefaultValue[0] + 1
|
||||
message = fmt.Sprintf("%s [%d]", message, defaultValue)
|
||||
}
|
||||
|
||||
for index, option := range options {
|
||||
fmt.Printf(" %d: %s\n", index+1, option)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
selectedValue := -1
|
||||
|
||||
for {
|
||||
choice := Prompt("Please choose an option")
|
||||
choice := Prompt(message)
|
||||
if choice == "" && defaultValue > -1 {
|
||||
selectedValue = defaultValue - 1
|
||||
break
|
||||
}
|
||||
|
||||
// index
|
||||
number, err := strconv.Atoi(choice)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package cmd
|
||||
+14
-11
@@ -81,21 +81,24 @@ func (s *SystemHelper) BackupConfig() (string, error) {
|
||||
|
||||
func (s *SystemHelper) setup() error {
|
||||
|
||||
systemConfig := make(map[string]string)
|
||||
|
||||
// Try to load current values - ignore errors
|
||||
config, err := s.LoadConfig()
|
||||
defaultName := ""
|
||||
defaultEmail := ""
|
||||
if config != nil {
|
||||
defaultName = config.Name
|
||||
defaultEmail = config.Email
|
||||
config, _ := s.LoadConfig()
|
||||
|
||||
if config.Name != "" {
|
||||
systemConfig["name"] = PromptRequired("What is your name", config.Name)
|
||||
} else {
|
||||
systemConfig["name"] = PromptRequired("What is your name")
|
||||
}
|
||||
if config.Email != "" {
|
||||
systemConfig["email"] = PromptRequired("What is your email address", config.Email)
|
||||
} else {
|
||||
systemConfig["email"] = PromptRequired("What is your email address")
|
||||
}
|
||||
|
||||
systemConfig := make(map[string]string)
|
||||
systemConfig["name"] = PromptRequired("What is your name", defaultName)
|
||||
systemConfig["email"] = PromptRequired("What is your email address", defaultEmail)
|
||||
|
||||
// Create the directory
|
||||
err = s.fs.MkDirs(s.wailsSystemDir)
|
||||
err := s.fs.MkDirs(s.wailsSystemDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+108
-231
@@ -3,112 +3,95 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
mewn "github.com/leaanthony/mewn"
|
||||
mewnlib "github.com/leaanthony/mewn/lib"
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
const templateSuffix = ".template"
|
||||
|
||||
// TemplateHelper helps with creating projects
|
||||
type TemplateHelper struct {
|
||||
system *SystemHelper
|
||||
fs *FSHelper
|
||||
templateDir string
|
||||
// templates map[string]string
|
||||
templateSuffix string
|
||||
metadataFilename string
|
||||
// TemplateMetadata holds all the metadata for a Wails template
|
||||
type TemplateMetadata struct {
|
||||
Name string `json:"name"`
|
||||
ShortDescription string `json:"shortdescription"`
|
||||
Description string `json:"description"`
|
||||
Install string `json:"install"`
|
||||
Build string `json:"build"`
|
||||
Author string `json:"author"`
|
||||
Created string `json:"created"`
|
||||
FrontendDir string `json:"frontenddir"`
|
||||
Serve string `json:"serve"`
|
||||
Bridge string `json:"bridge"`
|
||||
}
|
||||
|
||||
// Template defines a single template
|
||||
// TemplateDetails holds information about a specific template
|
||||
type TemplateDetails struct {
|
||||
BasePath string
|
||||
Path string
|
||||
Metadata *TemplateMetadata
|
||||
}
|
||||
|
||||
// TemplateList is a list of available templates
|
||||
type TemplateList struct {
|
||||
details map[string]*TemplateDetails
|
||||
}
|
||||
|
||||
// NewTemplateList creates a new TemplateList object
|
||||
func NewTemplateList(filenames *mewnlib.FileGroup) *TemplateList {
|
||||
// Iterate each template and store information
|
||||
|
||||
result := &TemplateList{details: make(map[string]*TemplateDetails)}
|
||||
|
||||
entries := slicer.String()
|
||||
entries.AddSlice(filenames.Entries())
|
||||
|
||||
// Find all template.json files
|
||||
metadataFiles := entries.Filter(func(filename string) bool {
|
||||
match, _ := regexp.MatchString("(.)+template.json$", filename)
|
||||
return match
|
||||
})
|
||||
|
||||
// Load each metadata file
|
||||
metadataFiles.Each(func(filename string) {
|
||||
fileData := filenames.Bytes(filename)
|
||||
var metadata TemplateMetadata
|
||||
err := json.Unmarshal(fileData, &metadata)
|
||||
if err != nil {
|
||||
log.Fatalf("corrupt metadata for template: %s", filename)
|
||||
}
|
||||
path := strings.Split(filename, string(filepath.Separator))[0]
|
||||
thisTemplate := &TemplateDetails{Path: path, Metadata: &metadata}
|
||||
result.details[filename] = thisTemplate
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Template holds details about a Wails template
|
||||
type Template struct {
|
||||
Name string
|
||||
Dir string
|
||||
Metadata map[string]interface{}
|
||||
Name string
|
||||
Path string
|
||||
Description string
|
||||
}
|
||||
|
||||
// TemplateHelper is a utility object to help with processing templates
|
||||
type TemplateHelper struct {
|
||||
TemplateList *TemplateList
|
||||
Files *mewnlib.FileGroup
|
||||
}
|
||||
|
||||
// NewTemplateHelper creates a new template helper
|
||||
func NewTemplateHelper() *TemplateHelper {
|
||||
result := TemplateHelper{
|
||||
system: NewSystemHelper(),
|
||||
fs: NewFSHelper(),
|
||||
templateSuffix: ".template",
|
||||
metadataFilename: "template.json",
|
||||
}
|
||||
// Calculate template base dir
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
result.templateDir = filepath.Join(path.Dir(filename), "templates")
|
||||
// result.templateDir = filepath.Join(result.system.homeDir, "go", "src", "github.com", "wailsapp", "wails", "cmd", "templates")
|
||||
return &result
|
||||
}
|
||||
files := mewn.Group("./templates")
|
||||
|
||||
// GetTemplateNames returns a map of all available templates
|
||||
func (t *TemplateHelper) GetTemplateNames() (map[string]string, error) {
|
||||
templateDirs, err := t.fs.GetSubdirs(t.templateDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return &TemplateHelper{
|
||||
TemplateList: NewTemplateList(files),
|
||||
Files: files,
|
||||
}
|
||||
return templateDirs, nil
|
||||
}
|
||||
|
||||
// GetTemplateDetails returns a map of Template structs containing details
|
||||
// of the found templates
|
||||
func (t *TemplateHelper) GetTemplateDetails() (map[string]*Template, error) {
|
||||
templateDirs, err := t.fs.GetSubdirs(t.templateDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]*Template)
|
||||
|
||||
for name, dir := range templateDirs {
|
||||
result[name] = &Template{
|
||||
Dir: dir,
|
||||
}
|
||||
metadata, err := t.LoadMetadata(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[name].Metadata = metadata
|
||||
if metadata["name"] != nil {
|
||||
result[name].Name = metadata["name"].(string)
|
||||
} else {
|
||||
// Ignore bad templates?
|
||||
result[name] = nil
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LoadMetadata loads the template's 'metadata.json' file
|
||||
func (t *TemplateHelper) LoadMetadata(dir string) (map[string]interface{}, error) {
|
||||
templateFile := filepath.Join(dir, t.metadataFilename)
|
||||
result := make(map[string]interface{})
|
||||
if !t.fs.FileExists(templateFile) {
|
||||
return nil, nil
|
||||
}
|
||||
rawJSON, err := ioutil.ReadFile(templateFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(rawJSON, &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// TemplateExists returns true if the given template name exists
|
||||
func (t *TemplateHelper) TemplateExists(templateName string) (bool, error) {
|
||||
templates, err := t.GetTemplateNames()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, exists := templates[templateName]
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// InstallTemplate installs the template given in the project options to the
|
||||
@@ -116,161 +99,55 @@ func (t *TemplateHelper) TemplateExists(templateName string) (bool, error) {
|
||||
func (t *TemplateHelper) InstallTemplate(projectPath string, projectOptions *ProjectOptions) error {
|
||||
|
||||
// Get template files
|
||||
template, err := t.getTemplateFiles(projectOptions.Template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
templatePath := projectOptions.selectedTemplate.Path
|
||||
|
||||
// Copy files to target
|
||||
err = template.Install(projectPath, projectOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
templateFilenames := slicer.String()
|
||||
templateFilenames.AddSlice(projectOptions.templates.Files.Entries())
|
||||
|
||||
return nil
|
||||
}
|
||||
templateJSONFilename := filepath.Join(templatePath, "template.json")
|
||||
|
||||
// templateFiles categorises files found in a template
|
||||
type templateFiles struct {
|
||||
BaseDir string
|
||||
StandardFiles []string
|
||||
Templates []string
|
||||
Dirs []string
|
||||
}
|
||||
|
||||
// newTemplateFiles returns a new TemplateFiles struct
|
||||
func (t *TemplateHelper) newTemplateFiles(dir string) *templateFiles {
|
||||
pathsep := string(os.PathSeparator)
|
||||
// Ensure base directory has trailing slash
|
||||
if !strings.HasSuffix(dir, pathsep) {
|
||||
dir = dir + pathsep
|
||||
}
|
||||
return &templateFiles{
|
||||
BaseDir: dir,
|
||||
}
|
||||
}
|
||||
|
||||
// AddStandardFile adds the given file to the list of standard files
|
||||
func (t *templateFiles) AddStandardFile(filename string) {
|
||||
localPath := strings.TrimPrefix(filename, t.BaseDir)
|
||||
t.StandardFiles = append(t.StandardFiles, localPath)
|
||||
}
|
||||
|
||||
// AddTemplate adds the given file to the list of template files
|
||||
func (t *templateFiles) AddTemplate(filename string) {
|
||||
localPath := strings.TrimPrefix(filename, t.BaseDir)
|
||||
t.Templates = append(t.Templates, localPath)
|
||||
}
|
||||
|
||||
// AddDir adds the given directory to the list of template dirs
|
||||
func (t *templateFiles) AddDir(dir string) {
|
||||
localPath := strings.TrimPrefix(dir, t.BaseDir)
|
||||
t.Dirs = append(t.Dirs, localPath)
|
||||
}
|
||||
|
||||
// getTemplateFiles returns a struct categorising files in
|
||||
// the template directory
|
||||
func (t *TemplateHelper) getTemplateFiles(templateName string) (*templateFiles, error) {
|
||||
|
||||
templates, err := t.GetTemplateNames()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
templateDir := templates[templateName]
|
||||
result := t.newTemplateFiles(templateDir)
|
||||
var localPath string
|
||||
err = filepath.Walk(templateDir, func(dir string, info os.FileInfo, err error) error {
|
||||
if dir == templateDir {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't copy template metadata
|
||||
localPath = strings.TrimPrefix(dir, templateDir+string(filepath.Separator))
|
||||
if localPath == t.metadataFilename {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Categorise the file
|
||||
switch {
|
||||
case info.IsDir():
|
||||
result.AddDir(dir)
|
||||
case strings.HasSuffix(info.Name(), templateSuffix):
|
||||
result.AddTemplate(dir)
|
||||
default:
|
||||
result.AddStandardFile(dir)
|
||||
}
|
||||
return nil
|
||||
templateFiles := templateFilenames.Filter(func(filename string) bool {
|
||||
return strings.HasPrefix(filename, templatePath) && filename != templateJSONFilename
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error processing template '%s' in path '%q': %v", templateName, templateDir, err)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Install the template files into the given project path
|
||||
func (t *templateFiles) Install(projectPath string, projectOptions *ProjectOptions) error {
|
||||
|
||||
fs := NewFSHelper()
|
||||
|
||||
// Create directories
|
||||
var targetDir string
|
||||
for _, dirname := range t.Dirs {
|
||||
targetDir = filepath.Join(projectPath, dirname)
|
||||
fs.MkDir(targetDir)
|
||||
}
|
||||
|
||||
// Copy standard files
|
||||
var targetFile, sourceFile string
|
||||
var err error
|
||||
for _, filename := range t.StandardFiles {
|
||||
sourceFile = filepath.Join(t.BaseDir, filename)
|
||||
targetFile = filepath.Join(projectPath, filename)
|
||||
templateFiles.Each(func(templateFile string) {
|
||||
|
||||
err = fs.CopyFile(sourceFile, targetFile)
|
||||
// Setup filenames
|
||||
relativeFilename := strings.TrimPrefix(templateFile, templatePath)[1:]
|
||||
targetFilename, err := filepath.Abs(filepath.Join(projectOptions.OutputDirectory, relativeFilename))
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
filedata := projectOptions.templates.Files.Bytes(templateFile)
|
||||
|
||||
// Do we have template files?
|
||||
if len(t.Templates) > 0 {
|
||||
|
||||
// Iterate over the templates
|
||||
var templateFile string
|
||||
var tmpl *template.Template
|
||||
for _, filename := range t.Templates {
|
||||
|
||||
// Load template text
|
||||
templateFile = filepath.Join(t.BaseDir, filename)
|
||||
templateText, err := fs.LoadAsString(templateFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply template
|
||||
tmpl = template.New(templateFile)
|
||||
tmpl.Parse(templateText)
|
||||
|
||||
// Write the template to a buffer
|
||||
// If file is a template, process it
|
||||
if strings.HasSuffix(templateFile, ".template") {
|
||||
templateData := projectOptions.templates.Files.String(templateFile)
|
||||
tmpl := template.New(templateFile)
|
||||
tmpl.Parse(templateData)
|
||||
var tpl bytes.Buffer
|
||||
err = tmpl.Execute(&tpl, projectOptions)
|
||||
if err != nil {
|
||||
fmt.Println("ERROR!!! " + err.Error())
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
// Save buffer to disk
|
||||
targetFilename := strings.TrimSuffix(filename, templateSuffix)
|
||||
targetFile = filepath.Join(projectPath, targetFilename)
|
||||
err = ioutil.WriteFile(targetFile, tpl.Bytes(), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Remove template suffix
|
||||
targetFilename = strings.TrimSuffix(targetFilename, ".template")
|
||||
|
||||
// Set the filedata to the template result
|
||||
filedata = tpl.Bytes()
|
||||
}
|
||||
|
||||
// Normal file, just copy it
|
||||
err = fs.CreateFile(targetFilename, filedata)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user