ionicon/ionicon_generate.go
jolheiser 879ffe62fc
Add title to SVGs
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2019-12-08 15:11:19 -06:00

330 lines
6.6 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
ioniconVersion = "4.6.3"
)
const (
tgzName = "ionicons.tgz"
dirName = "ionicons"
)
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 ionicons
resp, err := http.Get(fmt.Sprintf("https://registry.npmjs.org/ionicons/-/ionicons-%s.tgz", ioniconVersion))
if err != nil {
return fmt.Errorf("get ionicons: %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 ionicons: %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 ionicons: %v", err)
}
// Generate
ionicons := make(map[string]string, 0)
if err := filepath.Walk(dirName+"/package/dist/ionicons/svg", 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")
data, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("read file '%s': %v", path, err)
}
ionicons[name] = string(data)
return nil
}); err != nil {
return fmt.Errorf("walk svgs: %v", err)
}
var names []string
for name := range ionicons {
names = append(names, name)
}
sort.Strings(names)
var buf bytes.Buffer
fmt.Fprint(&buf, `package ionicon
import (
"fmt"
"html/template"
)
// Icons is a list of all Ionicons
var Icons = []string{"`)
fmt.Fprintf(&buf, strings.Join(names, `", "`))
fmt.Fprintf(&buf, `"}
// Ionicon represents an SVG node
type Ionicon struct {
xml string
width int
height int
style string
id string
class string
}
// XML returns the SVG node as an XML string
func (i *Ionicon) XML() string {
return fmt.Sprintf(i.xml, i.width, i.height, i.style, i.id, i.class)
}
// HTML returns the SVG node as an HTML template, safe for use in Go templates
func (i *Ionicon) HTML() template.HTML {
return template.HTML(i.XML())
}
// Size sets the size of an Ionicon
// Short for calling Width and Height with the same int
func (i *Ionicon) Size(size int) {
i.Width(size)
i.Height(size)
}
// Width sets the width of an Ionicon
func (i *Ionicon) Width(width int) {
i.width = width
}
// Height sets the height of an Ionicon
func (i *Ionicon) Height(height int) {
i.height = height
}
// Style sets the style of an Ionicon
func (i *Ionicon) Style(style string) {
i.style = style
}
// Id sets the id of an Ionicon
func (i *Ionicon) Id(id string) {
i.id = id
}
// Class sets the class of an Ionicon
func (i *Ionicon) Class(class string) {
i.class = class
}
// Icon returns the named Ionicon SVG node.
// It returns nil if name is not a valid Ionicon symbol name.
func Icon(name string) *Ionicon {
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 Ionicon functions.
for _, name := range names {
generateAndWriteIonicon(&buf, ionicons, 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 ioniconTemplate = template.Must(template.New("ionicon").Parse(`return &Ionicon{
xml: ` + "`" + `{{.xml}}` + "`" + `,
width: 16,
height: 16,
style: "display: inline-block; vertical-align: text-top; fill: currentColor;",
}
`))
func generateAndWriteIonicon(w io.Writer, ionicons map[string]string, name string) {
fmt.Fprintln(w)
fmt.Fprintf(w, "// %s returns the %q Ionicon.\n", kebab(name), name)
fmt.Fprintf(w, "func %s() *Ionicon {\n", kebab(name))
data := ionicons[name]
page, err := html.Parse(bytes.NewBufferString(data))
if err != nil {
fmt.Println(err)
return
}
node := page.FirstChild.LastChild.FirstChild
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
}
ioniconTemplate.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()
}
}