// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build ignore // This program generates table.go from // https://material.google.com/style/color.html package main import ( "bytes" "fmt" "go/format" "image/color" "io" "io/ioutil" "log" "net/http" "strings" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) type matchFunc func(*html.Node) bool func appendAll(dst []*html.Node, n *html.Node, mf matchFunc) []*html.Node { if mf(n) { dst = append(dst, n) } for c := n.FirstChild; c != nil; c = c.NextSibling { dst = appendAll(dst, c, mf) } return dst } func match(a atom.Atom, namespace, key, value string) matchFunc { return func(n *html.Node) bool { return n.DataAtom == a && strings.HasPrefix(getAttr(n, namespace, key), value) } } func getAttr(n *html.Node, namespace, key string) string { for _, attr := range n.Attr { if attr.Namespace == namespace && attr.Key == key { return attr.Val } } return "" } func getText(n *html.Node) string { if n.FirstChild == nil || n.FirstChild.Type != html.TextNode { return "" } return n.FirstChild.Data } var unhex = [256]byte{ '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15, } func parseRGB(s string) color.RGBA { if len(s) != 7 || s[0] != '#' { return color.RGBA{} } return color.RGBA{ R: unhex[s[1]]<<4 | unhex[s[2]], G: unhex[s[3]]<<4 | unhex[s[4]], B: unhex[s[5]]<<4 | unhex[s[6]], A: 0xff, } } type entry struct { name string rgba color.RGBA } func extractColors(tree *html.Node) (ret []entry) { for _, table := range appendAll(nil, tree, match(atom.Section, "", "class", "color-group")) { name := "" for _, nameNode := range appendAll(nil, table, match(atom.Span, "", "class", "name")) { name = strings.Replace(getText(nameNode), " ", "", -1) break } shades := appendAll(nil, table, match(atom.Span, "", "class", "shade")) hexes := appendAll(nil, table, match(atom.Span, "", "class", "hex")) if len(shades) != len(hexes) || len(shades) == 0 { continue } // Remove the duplicated 500 shade at the start of the list. if getText(shades[0]) == "500" { shades, hexes = shades[1:], hexes[1:] } for i, shade := range shades { ret = append(ret, entry{ name + getText(shade), parseRGB(getText(hexes[i])), }) } } return ret } const preamble = `// generated by go generate; DO NOT EDIT. package colornames import "image/color" ` func writeColorNames(w io.Writer, entries []entry) { fmt.Fprintln(w, preamble) fmt.Fprintln(w, "// Map contains named colors defined in the Material Design style guide.") fmt.Fprintln(w, "var Map = map[string]color.RGBA{") for _, e := range entries { c := e.rgba fmt.Fprintf(w, "%q:color.RGBA{%#02x, %#02x, %#02x, %#02x}, // rgb(%d, %d, %d)\n", e.name, c.R, c.G, c.B, c.A, c.R, c.G, c.B) } fmt.Fprintln(w, "}\n") fmt.Fprintln(w, "// Names contains the color names defined in the Material Design style guide.") fmt.Fprintln(w, "var Names = []string{") for _, e := range entries { fmt.Fprintf(w, "%q,\n", e.name) } fmt.Fprintln(w, "}\n") fmt.Fprintln(w, "var (") for _, e := range entries { c := e.rgba fmt.Fprintf(w, "%s=color.RGBA{%#02x, %#02x, %#02x, %#02x} // rgb(%d, %d, %d)\n", e.name, c.R, c.G, c.B, c.A, c.R, c.G, c.B) } fmt.Fprintln(w, ")") } const url = "https://material.google.com/style/color.html" func main() { res, err := http.Get(url) if err != nil { log.Fatalf("Couldn't read from %s: %s\n", url, err) } defer res.Body.Close() tree, err := html.Parse(res.Body) if err != nil { log.Fatalf("Couldn't parse %s: %s\n", url, err) } buf := &bytes.Buffer{} writeColorNames(buf, extractColors(tree)) fmted, err := format.Source(buf.Bytes()) if err != nil { log.Fatalf("Error while formatting code: %s\n", err) } if err := ioutil.WriteFile("table.go", fmted, 0644); err != nil { log.Fatalf("Error writing table.go: %s\n", err) } }