353 lines
7.2 KiB
Go
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()
|
|
}
|
|
}
|