330 lines
6.6 KiB
Go
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()
|
|
}
|
|
}
|