coreui/coreui_generate.go
jolheiser dba3ae4fec
Initial commit
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2019-12-12 22:36:18 -06:00

353 lines
7.2 KiB
Go

// +build ignore
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"flag"
"fmt"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"text/template"
)
var (
oFlag string
coreuiVersion = "1.0.0"
)
const (
tgzName = "coreuis.tgz"
dirName = "coreuis"
)
func init() {
flag.StringVar(&oFlag, "o", "", "write output to `file` (default standard output)")
}
func main() {
flag.Parse()
err := run()
if err != nil {
log.Fatalln(err)
}
}
func run() error {
// Get coreuis
resp, err := http.Get(fmt.Sprintf("https://registry.npmjs.org/@coreui/icons/-/icons-%s.tgz", coreuiVersion))
if err != nil {
return fmt.Errorf("get coreuis: %v", err)
}
defer resp.Body.Close()
tgz, err := os.Create(tgzName)
if err != nil {
return fmt.Errorf("create %s: %v", tgzName, err)
}
defer os.Remove(tgzName)
if _, err = io.Copy(tgz, resp.Body); err != nil {
return fmt.Errorf("copy coreuis: %v", err)
}
if err := tgz.Close(); err != nil {
return fmt.Errorf("close %s: %v", tgzName, err)
}
tgz, err = os.Open(tgzName)
if err != nil {
return fmt.Errorf("open %s: %v", tgzName, err)
}
// Unzip
if err := os.MkdirAll(dirName, os.ModePerm); err != nil {
return fmt.Errorf("mkdirall %s: %v", dirName, err)
}
defer os.RemoveAll(dirName)
if err := unzip(dirName, tgz); err != nil {
return fmt.Errorf("unzip coreuis: %v", err)
}
// Generate
coreuis := make(map[string]string, 0)
types := []string{"brand", "flag", "free"}
for _, t := range types {
if err := filepath.Walk(fmt.Sprintf("%s/package/svg/%s", dirName, t), func(path string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return fmt.Errorf("walk error: %v", walkErr)
}
if info.IsDir() {
return nil
}
name := strings.TrimSuffix(filepath.Base(path), ".svg")[4:]
if t == "free" {
name = fmt.Sprintf("linear-%s", name)
} else {
name = fmt.Sprintf("%s-%s", t, name)
}
data, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("read file '%s': %v", path, err)
}
coreuis[name] = string(data)
return nil
}); err != nil {
return fmt.Errorf("walk svgs: %v", err)
}
}
var names []string
for name := range coreuis {
names = append(names, name)
}
sort.Strings(names)
var buf bytes.Buffer
fmt.Fprint(&buf, `package coreui
import (
"fmt"
"html/template"
)
// Icons is a list of all CoreUI icons
var Icons = []string{"`)
fmt.Fprintf(&buf, strings.Join(names, `", "`))
fmt.Fprintf(&buf, `"}
// CoreUIIcon represents an SVG node
type CoreUIIcon struct {
xml string
width int
height int
style string
id string
class string
}
// XML returns the SVG node as an XML string
func (c *CoreUIIcon) XML() string {
return fmt.Sprintf(c.xml, c.width, c.height, c.style, c.id, c.class)
}
// HTML returns the SVG node as an HTML template, safe for use in Go templates
func (c *CoreUIIcon) HTML() template.HTML {
return template.HTML(c.XML())
}
// Size sets the size of a CoreUIIcon
// Short for calling Width and Height with the same int
func (c *CoreUIIcon) Size(size int) {
c.Width(size)
c.Height(size)
}
// Width sets the width of a CoreUIIcon
func (c *CoreUIIcon) Width(width int) {
c.width = width
}
// Height sets the height of a CoreUIIcon
func (c *CoreUIIcon) Height(height int) {
c.height = height
}
// Style sets the style of a CoreUIIcon
func (c *CoreUIIcon) Style(style string) {
c.style = style
}
// Id sets the id of a CoreUIIcon
func (c *CoreUIIcon) Id(id string) {
c.id = id
}
// Class sets the class of a CoreUIIcon
func (c *CoreUIIcon) Class(class string) {
c.class = class
}
// Icon returns the named CoreUI SVG node.
// It returns nil if name is not a valid CoreUIIcon symbol name.
func Icon(name string) *CoreUIIcon {
switch name {
`)
for _, name := range names {
fmt.Fprintf(&buf, " case %q:\n return %v()\n", name, kebab(name))
}
fmt.Fprint(&buf, ` default:
return nil
}
}
`)
// Write all individual CoreUI functions.
for _, name := range names {
generateAndWriteCoreUI(&buf, coreuis, name)
}
var w io.Writer
switch oFlag {
case "":
w = os.Stdout
default:
f, err := os.Create(oFlag)
if err != nil {
return err
}
defer f.Close()
w = f
}
_, err = w.Write(buf.Bytes())
return err
}
var coreuiTemplate = template.Must(template.New("coreui").Parse(`return &CoreUIIcon{
xml: ` + "`" + `{{.xml}}` + "`" + `,
width: 16,
height: 16,
style: "display: inline-block; vertical-align: text-top;",
}
`))
func generateAndWriteCoreUI(w io.Writer, coreuis map[string]string, name string) {
fmt.Fprintln(w)
fmt.Fprintf(w, "// %s returns the %q CoreUIIcon.\n", kebab(name), name)
fmt.Fprintf(w, "func %s() *CoreUIIcon {\n", kebab(name))
data := coreuis[name]
page, err := html.Parse(bytes.NewBufferString(data))
if err != nil {
fmt.Println(err)
return
}
node := page.FirstChild
if node.Type == html.CommentNode {
node = node.NextSibling
}
node = node.LastChild.FirstChild
idxs := make([]int, 0)
for idx, attr := range node.Attr {
if attr.Key == "width" || attr.Key == "height" || attr.Key == "style" || attr.Key == "class" {
idxs = append(idxs, idx)
}
}
for jdx := len(idxs) - 1; jdx >= 0; jdx-- {
node.Attr = append(node.Attr[:idxs[jdx]], node.Attr[idxs[jdx]+1:]...)
}
node.Attr = append(node.Attr,
html.Attribute{Key: "width", Val: "%d"},
html.Attribute{Key: "height", Val: "%d"},
html.Attribute{Key: "style", Val: "%s"},
html.Attribute{Key: "id", Val: "%s"},
html.Attribute{Key: "class", Val: "%s"},
)
node.InsertBefore(&html.Node{
FirstChild: &html.Node{
Type: html.TextNode,
Data: name,
Namespace: "svg",
},
Type: html.ElementNode,
DataAtom: atom.Title,
Data: "title",
Namespace: "svg",
}, node.FirstChild)
xml := &strings.Builder{}
if err := html.Render(xml, node); err != nil {
fmt.Println(err)
return
}
coreuiTemplate.Execute(w, map[string]interface{}{
"xml": xml.String(),
})
fmt.Fprintln(w, "}")
}
func kebab(input string) string {
parts := make([]string, strings.Count(input, "-")+1)
for idx, part := range strings.Split(input, "-") {
parts[idx] = strings.Title(part)
}
return strings.Join(parts, "")
}
func unzip(dst string, r io.Reader) error {
gzr, err := gzip.NewReader(r)
if err != nil {
return fmt.Errorf("gzip reader: %v", err)
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
switch {
// if no more files are found return
case err == io.EOF:
return nil
// return any other error
case err != nil:
return err
// if the header is nil, just skip it (not sure how this happens)
case header == nil:
continue
}
// the target location where the dir/file should be created
target := filepath.Join(dst, header.Name)
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return err
}
}
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return err
}
// copy over contents
if _, err := io.Copy(f, tr); err != nil {
return err
}
// manually close here after each file operation; deferring would cause each file close
// to wait until all operations have completed.
f.Close()
}
}