File: generate.go

package info (click to toggle)
golang-github-shurcool-octicon 0.0~git20230705.66bff05-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 508 kB
  • sloc: makefile: 2
file content (161 lines) | stat: -rw-r--r-- 3,988 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//go:build ignore

package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"dmitri.shuralyov.com/text/kebabcase"
	"github.com/shurcooL/go-goon"
	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

var oFlag = flag.String("o", "", "write output to `file` (default standard output)")

func main() {
	flag.Parse()

	err := run()
	if err != nil {
		log.Fatalln(err)
	}
}

func run() error {
	f, err := os.Open(filepath.Join("_data", "data.json"))
	if err != nil {
		return err
	}
	defer f.Close()

	var octicons map[string]octicon
	err = json.NewDecoder(f).Decode(&octicons)
	if err != nil {
		return err
	}

	var names []string
	for name := range octicons {
		names = append(names, name)
	}
	sort.Strings(names)

	var buf bytes.Buffer
	fmt.Fprint(&buf, `package octicon

import (
	"strconv"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

// Icon returns the named Octicon SVG node.
// It returns nil if name is not a valid Octicon symbol name.
func Icon(name string) *html.Node {
	switch name {
`)
	for _, name := range names {
		fmt.Fprintf(&buf, "	case %q:\n		return %v()\n", name, kebabcase.Parse(name).ToMixedCaps())
	}
	fmt.Fprint(&buf, `	default:
		return nil
	}
}

// SetSize sets size of icon, and returns a reference to it.
func SetSize(icon *html.Node, size int) *html.Node {
	icon.Attr[`, widthAttrIndex, `].Val = strconv.Itoa(size)
	icon.Attr[`, heightAttrIndex, `].Val = strconv.Itoa(size)
	return icon
}
`)

	// Write all individual Octicon functions.
	for _, name := range names {
		generateAndWriteOcticon(&buf, octicons, 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
}

type octicon struct {
	Path   string
	Width  int
	Height float64
}

func generateAndWriteOcticon(w io.Writer, octicons map[string]octicon, name string) {
	svgXML := generateOcticon(octicons[name])

	svg := parseOcticon(svgXML)
	// Clear these fields to remove cycles in the data structure, since go-goon
	// cannot print those in a way that's valid Go code. The generated data structure
	// is not a proper *html.Node with all fields set, but it's enough for rendering
	// to be successful.
	svg.LastChild = nil
	svg.FirstChild.Parent = nil

	fmt.Fprintln(w)
	fmt.Fprintf(w, "// %s returns an %q Octicon SVG node.\n", kebabcase.Parse(name).ToMixedCaps(), name)
	fmt.Fprintf(w, "func %s() *html.Node {\n", kebabcase.Parse(name).ToMixedCaps())
	fmt.Fprint(w, "	return ")
	goon.Fdump(w, svg)
	fmt.Fprintln(w, "}")
}

// These constants are used during generation of SetSize function.
// Keep them in sync with generateOcticon below.
const (
	widthAttrIndex  = 1
	heightAttrIndex = 2
)

// TODO: Short-circuit generateOcticon and parseOcticon.

func generateOcticon(o octicon) (svgXML string) {
	path := o.Path
	if strings.HasPrefix(path, `<path fill-rule="evenodd" `) {
		// Skip fill-rule, if present. It has no effect on displayed SVG, but takes up space.
		path = `<path ` + path[len(`<path fill-rule="evenodd" `):]
	}
	// Note, SetSize relies on the absolute position of the width, height attributes.
	// Keep them in sync with widthAttrIndex and heightAttrIndex.
	return fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width=16 height=16 viewBox="0 0 %v %v">%s</svg>`,
		o.Width, o.Height, path)
}

func parseOcticon(svgXML string) *html.Node {
	e, err := html.ParseFragment(strings.NewReader(svgXML), nil)
	if err != nil {
		panic(fmt.Errorf("internal error: html.ParseFragment failed: %v", err))
	}
	svg := e[0].LastChild.FirstChild // TODO: Is there a better way to just get the <svg>...</svg> element directly, skipping <html><head></head><body><svg>...</svg></body></html>?
	svg.Parent.RemoveChild(svg)
	svg.Attr = append(svg.Attr, html.Attribute{Key: atom.Style.String(), Val: `fill: currentColor; vertical-align: top;`})
	return svg
}