Merge commit '25a157e6614739fbd4df1da3d11d46afe72ae9a8' as 'v2'

This commit is contained in:
Travis McLane
2020-09-01 19:34:51 -05:00
208 changed files with 23636 additions and 0 deletions

108
v2/internal/html/asset.go Normal file
View File

@@ -0,0 +1,108 @@
package html
import (
"fmt"
"io/ioutil"
"net/url"
"path/filepath"
"regexp"
"strings"
"unsafe"
)
type assetTypes struct {
JS string
CSS string
FAVICON string
HTML string
}
// AssetTypes is an enum for the asset type keys
var AssetTypes *assetTypes = &assetTypes{
JS: "javascript",
CSS: "css",
FAVICON: "favicon",
HTML: "html",
}
// Asset describes an asset type and its path
type Asset struct {
Type string
Path string
Data string
}
// Load the asset from disk
func (a *Asset) Load(basedirectory string) error {
assetpath := filepath.Join(basedirectory, a.Path)
data, err := ioutil.ReadFile(assetpath)
if err != nil {
return err
}
a.Data = string(data)
return nil
}
// AsString returns the data as a READ ONLY string
func (a *Asset) AsString() string {
return a.Data
}
// AsCHexData processes the asset data so it may be used by C
func (a *Asset) AsCHexData() string {
// This will be our final string to hexify
dataString := a.Data
switch a.Type {
case AssetTypes.HTML:
// Escape HTML
var re = regexp.MustCompile(`\s{2,}`)
result := re.ReplaceAllString(a.Data, ``)
result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\r\n", "")
result = strings.ReplaceAll(result, "\n", "")
url := url.URL{Path: result}
urlString := strings.ReplaceAll(url.String(), "/", "%2f")
// Save Data uRI string
dataString = "data:text/html;charset=utf-8," + urlString
case AssetTypes.CSS:
// Escape CSS data
var re = regexp.MustCompile(`\s{2,}`)
result := re.ReplaceAllString(a.Data, ``)
result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\r\n", "")
result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\t", "")
result = strings.ReplaceAll(result, `\`, `\\`)
result = strings.ReplaceAll(result, `"`, `\"`)
result = strings.ReplaceAll(result, `'`, `\'`)
result = strings.ReplaceAll(result, ` {`, `{`)
result = strings.ReplaceAll(result, `: `, `:`)
dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result)
}
// Get byte data of the string
bytes := *(*[]byte)(unsafe.Pointer(&dataString))
// Create a strings builder
var cdata strings.Builder
// Set buffer size to 4k
cdata.Grow(4096)
// Convert each byte to hex
for _, b := range bytes {
cdata.WriteString(fmt.Sprintf("0x%x, ", b))
}
return cdata.String()
}
func (a *Asset) Dump() {
fmt.Printf("{ Type: %s, Path: %s, Data: %+v }\n", a.Type, a.Path, a.Data[:10])
}

View File

@@ -0,0 +1,195 @@
package html
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"
"github.com/leaanthony/slicer"
"github.com/leaanthony/wailsv2/v2/internal/assetdb"
"golang.org/x/net/html"
)
// AssetBundle is a collection of Assets
type AssetBundle struct {
assets []*Asset
basedirectory string
}
// NewAssetBundle creates a new AssetBundle struct containing
// the given html and all the assets referenced by it
func NewAssetBundle(pathToHTML string) (*AssetBundle, error) {
// Create result
result := &AssetBundle{
basedirectory: filepath.Dir(pathToHTML),
}
err := result.loadAssets(pathToHTML)
if err != nil {
return nil, err
}
return result, nil
}
// loadAssets processes the given html file and loads in
// all referenced assets
func (a *AssetBundle) loadAssets(pathToHTML string) error {
// Save HTML
htmlAsset := &Asset{
Type: AssetTypes.HTML,
Path: filepath.Base(pathToHTML),
}
err := htmlAsset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, htmlAsset)
return a.processHTML(htmlAsset.AsString())
}
// Credit to: https://drstearns.github.io/tutorials/tokenizing/
func (a *AssetBundle) processHTML(htmldata string) error {
// Tokenize the html
buf := bytes.NewBufferString(htmldata)
tokenizer := html.NewTokenizer(buf)
for {
//get the next token type
tokenType := tokenizer.Next()
//if it's an error token, we either reached
//the end of the file, or the HTML was malformed
if tokenType == html.ErrorToken {
err := tokenizer.Err()
if err == io.EOF {
//end of the file, break out of the loop
break
}
//otherwise, there was an error tokenizing,
//which likely means the HTML was malformed.
//since this is a simple command-line utility,
//we can just use log.Fatalf() to report the error
//and exit the process with a non-zero status code
return tokenizer.Err()
}
//process the token according to the token type...
if tokenType == html.StartTagToken {
//get the token
token := tokenizer.Token()
//if the name of the element is "title"
if "link" == token.Data {
//the next token should be the page title
tokenType = tokenizer.Next()
//just make sure it's actually a text token
asset := &Asset{}
for _, attr := range token.Attr {
// Favicon
if attr.Key == "rel" && attr.Val == "icon" {
asset.Type = AssetTypes.FAVICON
}
if attr.Key == "href" {
asset.Path = attr.Val
}
// stylesheet
if attr.Key == "rel" && attr.Val == "stylesheet" {
asset.Type = AssetTypes.CSS
}
}
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
}
if "script" == token.Data {
tokenType = tokenizer.Next()
//just make sure it's actually a text token
asset := &Asset{Type: AssetTypes.JS}
for _, attr := range token.Attr {
if attr.Key == "src" {
asset.Path = attr.Val
break
}
}
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
}
}
}
return nil
}
// WriteToCFile dumps all the assets to C files in the given directory
func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
// Write out the assets.c file
var cdata strings.Builder
// Write header
header := `// assets.c
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL.
// This file was auto-generated. DO NOT MODIFY.
`
cdata.WriteString(header)
// Loop over the Assets
var err error
assetVariables := slicer.String()
var variableName string
for index, asset := range a.assets {
variableName = fmt.Sprintf("%s%d", asset.Type, index)
assetCdata := fmt.Sprintf("const unsigned char %s[]={ %s0x00 };\n", variableName, asset.AsCHexData())
cdata.WriteString(assetCdata)
assetVariables.Add(variableName)
}
if assetVariables.Length() > 0 {
cdata.WriteString(fmt.Sprintf("\nconst char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
} else {
cdata.WriteString("\nconst char *assets[] = { 0x00 };")
}
// Save file
assetsFile := filepath.Join(targetDir, "assets.c")
err = ioutil.WriteFile(assetsFile, []byte(cdata.String()), 0600)
if err != nil {
return "", err
}
return assetsFile, nil
}
// ConvertToAssetDB returns an assetdb.AssetDB initialized with
// the items in the AssetBundle
func (a *AssetBundle) ConvertToAssetDB() (*assetdb.AssetDB, error) {
assetdb := assetdb.NewAssetDB()
// Loop over the Assets
for _, asset := range a.assets {
assetdb.AddAsset(asset.Path, []byte(asset.Data))
}
return assetdb, nil
}
func (a *AssetBundle) Dump() {
println("Assets:")
for _, asset := range a.assets {
asset.Dump()
}
}