diff --git a/fast/main.go b/fast/main.go new file mode 100644 index 0000000..9bfa25f --- /dev/null +++ b/fast/main.go @@ -0,0 +1,234 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "path/filepath" + "strings" + "time" + + "github.com/google/go-jsonnet" + "github.com/sh0rez/docsonnet/pkg/docsonnet" + "github.com/sh0rez/docsonnet/pkg/render" +) + +func main() { + data, err := eval() + if err != nil { + log.Fatalln(err) + } + // fmt.Println(string(data)) + + var d DS + if err := json.Unmarshal(data, &d); err != nil { + log.Fatalln(err) + } + + pkg := load(d) + + fmt.Println("render") + res := render.Render(pkg) + for k, v := range res { + fmt.Println(k) + if err := ioutil.WriteFile(filepath.Join("docs", k), []byte(v), 0644); err != nil { + log.Fatalln(err) + } + } +} + +// load docsonnet +// +// Data assumptions: +// - only map[string]interface{} and docsonnet fields +// - docsonnet fields (#...) coming first +func load(d DS) docsonnet.Package { + start := time.Now() + + pkg := d.Package() + fmt.Println("load", pkg.Name) + + pkg.API = make(docsonnet.Fields) + pkg.Sub = make(map[string]docsonnet.Package) + + for k, v := range d { + if k == "#" { + continue + } + + f := v.(map[string]interface{}) + + // docsonnet field + name := strings.TrimPrefix(k, "#") + if strings.HasPrefix(k, "#") { + pkg.API[name] = loadField(name, f, d) + continue + } + + // non-docsonnet + // subpackage? + if _, ok := f["#"]; ok { + p := load(DS(f)) + pkg.Sub[p.Name] = p + continue + } + + // non-annotated nested? + // try to load, but skip when already loaded as annotated above + if nested, ok := loadNested(name, f); ok && !fieldsHas(pkg.API, name) { + pkg.API[name] = *nested + continue + } + } + + fmt.Println("done load", pkg.Name, time.Since(start)) + return pkg +} + +func fieldsHas(f docsonnet.Fields, key string) bool { + _, b := f[key] + return b +} + +func loadNested(name string, msi map[string]interface{}) (*docsonnet.Field, bool) { + out := docsonnet.Object{ + Name: name, + Fields: make(docsonnet.Fields), + } + + ok := false + for k, v := range msi { + f := v.(map[string]interface{}) + n := strings.TrimPrefix(k, "#") + + if !strings.HasPrefix(k, "#") { + if l, ok := loadNested(k, f); ok { + out.Fields[n] = *l + } + continue + } + + ok = true + l := loadField(n, f, msi) + out.Fields[n] = l + } + + if !ok { + return nil, false + } + + return &docsonnet.Field{Object: &out}, true +} + +func loadField(name string, field map[string]interface{}, parent map[string]interface{}) docsonnet.Field { + if ifn, ok := field["function"]; ok { + return loadFn(name, ifn.(map[string]interface{})) + } + + if iobj, ok := field["object"]; ok { + return loadObj(name, iobj.(map[string]interface{}), parent) + } + + panic("docsonnet field lacking {function | object}") +} + +func loadFn(name string, msi map[string]interface{}) docsonnet.Field { + fn := docsonnet.Function{ + Name: name, + Help: msi["help"].(string), + } + if args, ok := msi["args"]; ok { + fn.Args = loadArgs(args.([]interface{})) + } + return docsonnet.Field{Function: &fn} +} + +func loadArgs(is []interface{}) []docsonnet.Argument { + args := make([]docsonnet.Argument, len(is)) + for i := range is { + arg := is[i].(map[string]interface{}) + args[i] = docsonnet.Argument{ + Name: arg["name"].(string), + Type: docsonnet.Type(arg["type"].(string)), + Default: arg["default"], + } + } + return args +} + +func fieldNames(msi map[string]interface{}) []string { + out := make([]string, 0, len(msi)) + for k := range msi { + out = append(out, k) + } + return out +} + +func loadObj(name string, msi map[string]interface{}, parent map[string]interface{}) docsonnet.Field { + obj := docsonnet.Object{ + Name: name, + Help: msi["help"].(string), + Fields: make(docsonnet.Fields), + } + + // look for children in same key without # + var iChilds interface{} + var ok bool + if iChilds, ok = parent[name]; !ok { + fmt.Println("aborting, no", name, strings.Join(fieldNames(parent), ", ")) + return docsonnet.Field{Object: &obj} + } + + childs := iChilds.(map[string]interface{}) + for k, v := range childs { + name := strings.TrimPrefix(k, "#") + f := v.(map[string]interface{}) + if !strings.HasPrefix(k, "#") { + if l, ok := loadNested(k, f); ok { + obj.Fields[name] = *l + } + continue + } + + obj.Fields[name] = loadField(name, f, childs) + } + + return docsonnet.Field{Object: &obj} +} + +type DS map[string]interface{} + +func (d DS) Package() docsonnet.Package { + hash, ok := d["#"] + if !ok { + log.Fatalln("Package declaration missing") + } + + pkg := hash.(map[string]interface{}) + return docsonnet.Package{ + Help: pkg["help"].(string), + Name: pkg["name"].(string), + Import: pkg["import"].(string), + } +} + +func eval() ([]byte, error) { + fmt.Println("eval start") + start := time.Now() + + vm := jsonnet.MakeVM() + vm.Importer(&jsonnet.FileImporter{JPaths: []string{".."}}) + data, err := ioutil.ReadFile("fast.libsonnet") + if err != nil { + return nil, err + } + + out, err := vm.EvaluateSnippet("fast.libsonnet", string(data)) + if err != nil { + return nil, err + } + + fmt.Println("eval:", time.Since(start)) + return []byte(out), nil +} diff --git a/go.sum b/go.sum index 2ab4017..3b466b6 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/go-clix/cli v0.1.1 h1:T9N0AdMbmpFM9cLw42TcLL5sQ3YgyxTyHUhBK0GW1LI= -github.com/go-clix/cli v0.1.1/go.mod h1:dYJevXraB9mXZFhz5clyQestG0qGcmT5rRC/P9etoRQ= github.com/go-clix/cli v0.1.2-0.20200502172020-b8f4629e879a h1:nh+UOawbjKgiUAJAgi8JHctNebEu6mjwDXsv8Xdln8w= github.com/go-clix/cli v0.1.2-0.20200502172020-b8f4629e879a/go.mod h1:dYJevXraB9mXZFhz5clyQestG0qGcmT5rRC/P9etoRQ= github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= diff --git a/load.libsonnet b/load.libsonnet index d97ca69..79dd84e 100644 --- a/load.libsonnet +++ b/load.libsonnet @@ -1,59 +1,24 @@ local lib = { - // reshape converts the Jsonnet structure to the one used by docsonnet: - // - put fields into an `api` key - // - put subpackages into `sub` key - reshape(pkg):: - local aux(old, key) = - if key == '#' then - old - else if std.objectHas(pkg[key], '#') then - old { sub+: { [key]: $.package(pkg[key]) } } - else - old { api+: { [key]: pkg[key] } }; - - std.foldl(aux, std.objectFields(pkg), {}) - + pkg['#'], - - // fillObjects creates docsonnet objects from Jsonnet ones, - // also filling those that have been specified explicitely - fillObjects(api):: + scan(obj):: local aux(old, key) = if std.startsWith(key, '#') then - old { [key]: api[key] } - else if std.isObject(api[key]) && std.length(std.objectFields(api[key])) > 0 then - old { ['#' + key]+: { object+: { - fields: api[key], - } } } + true + else if std.isObject(obj[key]) then + old || $.scan(obj[key]) else old; + std.foldl(aux, std.objectFieldsAll(obj), false), - std.foldl(aux, std.objectFields(api), {}), - - // clean removes all hashes from field names - clean(api):: { - [std.lstripChars(key, '#')]: - if std.isObject(api[key]) then $.clean(api[key]) - else api[key] - for key in std.objectFields(api) - }, - - cleanNonObj(api):: { - [key]: - if std.startsWith(key, "#") then api[key] - else if std.isObject(api[key]) then $.cleanNonObj(api[key]) - else api[key] - for key in std.objectFieldsAll(api) - if std.isObject(api[key]) - }, + load(pkg):: + local aux(old, key) = + if !std.isObject(pkg[key]) then + old + else if std.startsWith(key, '#') then + old { [key]: pkg[key] } + else if self.scan(pkg[key]) then + old { [key]: $.load(pkg[key]) } + else old; - // package loads docsonnet from a Jsonnet package - package(pkg):: - local cleaned = self.cleanNonObj(pkg); - local reshaped = self.reshape(cleaned); - local filled = - if std.objectHas(reshaped, 'api') - then reshaped { api: $.fillObjects(reshaped.api) } - else reshaped; - self.clean(filled), + std.foldl(aux, std.objectFieldsAll(pkg), {}), }; -lib.package(std.extVar("main")) +lib.load(std.extVar('main')) diff --git a/pkg/docsonnet/fast.go b/pkg/docsonnet/fast.go new file mode 100644 index 0000000..56e7b72 --- /dev/null +++ b/pkg/docsonnet/fast.go @@ -0,0 +1,183 @@ +package docsonnet + +import ( + "fmt" + "log" + "strings" + "time" +) + +// load docsonnet +// +// Data assumptions: +// - only map[string]interface{} and fields +// - fields (#...) coming first +func fastLoad(d DS) Package { + start := time.Now() + + pkg := d.Package() + fmt.Println("load", pkg.Name) + + pkg.API = make(Fields) + pkg.Sub = make(map[string]Package) + + for k, v := range d { + if k == "#" { + continue + } + + f := v.(map[string]interface{}) + + // field + name := strings.TrimPrefix(k, "#") + if strings.HasPrefix(k, "#") { + pkg.API[name] = loadField(name, f, d) + continue + } + + // non-docsonnet + // subpackage? + if _, ok := f["#"]; ok { + p := fastLoad(DS(f)) + pkg.Sub[p.Name] = p + continue + } + + // non-annotated nested? + // try to load, but skip when already loaded as annotated above + if nested, ok := loadNested(name, f); ok && !fieldsHas(pkg.API, name) { + pkg.API[name] = *nested + continue + } + } + + fmt.Println("done load", pkg.Name, time.Since(start)) + return pkg +} + +func fieldsHas(f Fields, key string) bool { + _, b := f[key] + return b +} + +func loadNested(name string, msi map[string]interface{}) (*Field, bool) { + out := Object{ + Name: name, + Fields: make(Fields), + } + + ok := false + for k, v := range msi { + f := v.(map[string]interface{}) + n := strings.TrimPrefix(k, "#") + + if !strings.HasPrefix(k, "#") { + if l, ok := loadNested(k, f); ok { + out.Fields[n] = *l + } + continue + } + + ok = true + l := loadField(n, f, msi) + out.Fields[n] = l + } + + if !ok { + return nil, false + } + + return &Field{Object: &out}, true +} + +func loadField(name string, field map[string]interface{}, parent map[string]interface{}) Field { + if ifn, ok := field["function"]; ok { + return loadFn(name, ifn.(map[string]interface{})) + } + + if iobj, ok := field["object"]; ok { + return loadObj(name, iobj.(map[string]interface{}), parent) + } + + panic("field lacking {function | object}") +} + +func loadFn(name string, msi map[string]interface{}) Field { + fn := Function{ + Name: name, + Help: msi["help"].(string), + } + if args, ok := msi["args"]; ok { + fn.Args = loadArgs(args.([]interface{})) + } + return Field{Function: &fn} +} + +func loadArgs(is []interface{}) []Argument { + args := make([]Argument, len(is)) + for i := range is { + arg := is[i].(map[string]interface{}) + args[i] = Argument{ + Name: arg["name"].(string), + Type: Type(arg["type"].(string)), + Default: arg["default"], + } + } + return args +} + +func fieldNames(msi map[string]interface{}) []string { + out := make([]string, 0, len(msi)) + for k := range msi { + out = append(out, k) + } + return out +} + +func loadObj(name string, msi map[string]interface{}, parent map[string]interface{}) Field { + obj := Object{ + Name: name, + Help: msi["help"].(string), + Fields: make(Fields), + } + + // look for children in same key without # + var iChilds interface{} + var ok bool + if iChilds, ok = parent[name]; !ok { + fmt.Println("aborting, no", name, strings.Join(fieldNames(parent), ", ")) + return Field{Object: &obj} + } + + childs := iChilds.(map[string]interface{}) + for k, v := range childs { + name := strings.TrimPrefix(k, "#") + f := v.(map[string]interface{}) + if !strings.HasPrefix(k, "#") { + if l, ok := loadNested(k, f); ok { + obj.Fields[name] = *l + } + continue + } + + obj.Fields[name] = loadField(name, f, childs) + } + + return Field{Object: &obj} +} + +type DS map[string]interface{} + +func (d DS) Package() Package { + hash, ok := d["#"] + if !ok { + log.Fatalln("Package declaration missing") + } + + pkg := hash.(map[string]interface{}) + return Package{ + Help: pkg["help"].(string), + Name: pkg["name"].(string), + Import: pkg["import"].(string), + } +} diff --git a/pkg/docsonnet/load.go b/pkg/docsonnet/load.go index 95c5134..9853836 100644 --- a/pkg/docsonnet/load.go +++ b/pkg/docsonnet/load.go @@ -32,18 +32,22 @@ func Load(filename string) (*Package, error) { // invoke load.libsonnet vm.ExtCode("main", fmt.Sprintf(`(import "%s")`, filename)) + + log.Println("evaluating Jsonnet") data, err := vm.EvaluateSnippet("load.libsonnet", string(load)) if err != nil { return nil, err } + log.Println("parsing result") // parse the result - var d Package + var d DS if err := json.Unmarshal([]byte(data), &d); err != nil { log.Fatalln(err) } - return &d, nil + p := fastLoad(d) + return &p, nil } // importer wraps jsonnet.FileImporter, to statically provide load.libsonnet, diff --git a/pkg/render/render.go b/pkg/render/render.go index 4f0e22d..792d2bf 100644 --- a/pkg/render/render.go +++ b/pkg/render/render.go @@ -15,6 +15,7 @@ func Render(pkg docsonnet.Package) map[string]string { } func render(pkg docsonnet.Package, parents []string, root bool) map[string]string { + fmt.Println("render", pkg.Name) link := "/" + strings.Join(append(parents, pkg.Name), "/") if root { link = "/" diff --git a/pkged.go b/pkged.go index 1e9f9d9..5f5807d 100644 --- a/pkged.go +++ b/pkged.go @@ -9,4 +9,4 @@ import ( "github.com/markbates/pkger/pkging/mem" ) -var _ = pkger.Apply(mem.UnmarshalEmbed([]byte(`1f8b08000000000000ffec5b5b93e2b892fe2b27fcba4c63cca50a22ce037637c65045355065639f98e8902523bb902f63d98099e8ffbe21f90a759d3eb31bbb2778e84697b494cafc325352aafe14bc601b5261f4a780bdc44ded2f30f4dbd41563e7d44621a461103809ebfeeac5c24868bba1efb4133b0600eeda8730de35a85a82e647619c7c07892b8cde1db0252c80ef0823c1075e20b484af21144682d0121e418cd9841733e1b06d7bc1d900ab307c49f782a37b90405718fd4bf822fcde12d609208e30da02429da2b672000d837c72359c78c4a18c9cf1f50587424bf041bc43e1a1a8453bec2056fcbd5c2d27770218222fc0ed6736584b70e2388ca9d012b63ee3a2210a1cfe0689776c43e25d76849838acffb9e2bed1cdd8b041e2d03663217eb793fdcf98f11dff9cee520f8cb0eda34f10519262a1257861db0bd3c423424b20216ba161cc38a549ec059832b17c75222e133bdd7a4c647696384c1430f4a3d8a1b4bd2520719a0df8e445bc1e24c00b9cb84d3c9a140dce9197e22c4ac2aad006f98879057a91cb2552d451b3135150571c785e4552bfdf19be68687b41e2c401206d071d408ce82519215e9478b06e717dd0a8559fc7204085b42ebb686a27c4a93b7cd4af2becbb460df61a95e602a80b3a6735a93f38abf73b52a37e3165421a723af6c5e179ad1dedbc23c37281ed46b10d68d069d66d409d41efacc50b409c355b20dd37abaed31cbc329cb21e71ecd6764400fe8be664a7db2d2061db7562e72f99da8bee36a09f21c14ef011550dac72619f238f404c2f8dfe3df238c431b8307e1750d783611cb1c90f3188deeac6e16f7e4a128ff3f86f78a18a1d1f44f493a409d83961d07e95d47522f0370dd3de7a24712e15f0ba1bfd94ab757cdb41bfe694dfa0a3090a2ff88b42ea30f7c81c277112e7fdde36bcf4ecaf51b4bd80268090bf395034c9a26da7db8e0a0b66302b7eda30865de69f2ab5b15001086e36c1286d56b77e52449caa2970922406d069b685b4046f6d13215f64c3379f7f123b5be2c08478c95933f5024c9c2df1b07b362bcd280484b49da3039d60ff5a571a70ff5983d2a1491e34795c7c194b7db671ca7fdab687eb6242cb7211197ccf778a9f36b7d50870a1f0863fd2307150147b41026ceef07317c754e42649d428f2ff4ae9558d25c7451b8bc1511cf2f8cbea69cc7a383843ca059097d2dc4545f92ad80f3333a7a817f2e525ec1ca3aad0a659900026a9380d927c6145a90df9a6abac21c7e6e02aeb95644112fa3c1cbfe82944faa29d66f46cf30243aec3721bd3126816c0e2a71ebed0acd012984cda89e347c55ee6ac9ebb6bd69aaf260d3c18a246a99d26dbcee0bc7ecbab146c19ddde095018b771484080bf84316e1fdbe56ec705d00592f839aa282459a72bf63fa0e63fccfa3e4b57ee00de234ee3bd53ee9cdea1737768fb3ec5cb4dd33bc41fac9801180594fdf31d4a017e6bb83313c12937c10fe9a2383c661f104a6d370270f70e958702f04637cd68e1125febe528a40e4c63a76d7bc88bd337a59503360601dd86b1ff1e51895136e067e80236deef2de1d1a1c9c589ea077381c5e98975bf73827a75a303fda80dfd88db1b4d586cfebd25dc87882d73f4a7f0a983e73d3b718e9238755a9f3bd4aae17d883e206be3f08b1f224ead3b31f5f891b2f3a5d3137efefcd912b6b914de3d658f58f1371610dafcf4493cbb3e7eb3633afb454e023cc2870aea137483b62550efe4082369707bdb127ce676463d49e4c51fdc1f8d044994c4dfc4de6fd2eda3d41d75c551affb45ec0c7ab73db17ffb5fa234129977f1e80fc4e4539c9699c71cfd297c75f6c26830e88a372d410b4261d4bdbd19de4ae26d4b58102fd809a30e5789238cbadd0ee3e1c943c2a8238a624b50ebe2e6c78f08205118892d6185d8a0624b58375897c92e5f494f1c0e5835843b2a8c6e5bc238f17cc6cada81c2a8d3bfbded88c35e6fd0121694b5dcf6ba9d41a777d3ffd912ee5f25ed96a4e59a7fb604e5d3949b1f3fd220a50e1246ff125b624bfc9dab981d413f754f52a9f9f2c2a4be156950bcbc19a9ef3eeafb8d2025e4f3d71b3975657c79353fb6e7e533d3ad9b2ebef83f6f78c55e8431b8c39fbd917ad30a7fb6040412208c04e710624d3dee4d6942357589bfafe593651cfdbb7138d79471a06570a0a9cb14aa49301f875853c6d8f62789f528069602a9e28db1a6c81de81f8633094548753ba6d77fb625710f257c80be3eb893567bb3bbd8db7e9f2065b8b3a5fb046d56915d7f1f5ac6f1f07d73bcdd1673688a8c2d75f87cb7d145601cb1369533bb6b4596aa9f34f51b5e07e5787264072b027dcb35a5276cf9c34c53e59d2d2d4eb6d44fadcd0adf55638eb1b996e7501aa6b6af8bdab7c972bd66b4f7d892f4d4dacc5ca40e336dba0acdcd12a3cd82c04c3ea1e9ac6376571de8eb3b4d5db948fd8681d13f2155772d45de5b9efc6c4b9dc432fa22cc7a733ed7b45ccb8e2a3864fcbba694b896f43478f0e49b7cdde34093fa046590b5edee7c2b9d2b28b536109bdd1931372b36bfaba98c463e98c622360d44b4299333c6964424cbe86153edbbb6a1e33b45c6c0e81cecee4c3415597fda1db069f4779a9acbd75464d1dab8e22c3b60435aa6e66616b0f94ca343589ba5f49fee8285087d925ad98ee1a1a461fa0dee147977479629ecae3260f483f99a7fe3421f8633c925f674fc924617a3bb0a377a77ce797da2da54cf6ce58081aa53a8ec9a32c15a4e33287e69d5aea0c8dec87b182c83078f63aeee53f9fc83e2b7c4d590cf3d15731d28f099e178760a07da74416cd54ca13a7906d224b0745ae802214da16c5d3e32facf7701c7b4c6e43757105f83a9c811d7cb741132590375925a8aecdb5d0d9bb59ed39c16e6eb9fcab1f5dabc6b865158f1674bb33f2c6321ce4e21e3616ffa6138eb701b88904a181ef83ccc3690d12176b08a6c1f36ed616ffa1131bbab93a6b8d8c9e4a1a9ecd259b6e33229c77f780e31b77d658c67d982f1107c0283c5d84baaa9c30352c9def627d49eee309288081479676d16cfd027799f27bbb6bfc4964f88adae4e17b8e398fc00773e30746a4def2b5c95fcbfc415d7fb709641aaa9566419c71d3c756eb6eb1c5b9a3af480af3fa3aff5ba4bbff3e0e5bfb59f90cb7907d558d9a1b06b116fd7877965bf5d8e0fb5e4b321c327a03e6173daec5f604dedec2d95b0f1334d1d4ad666960183cb5864f4a5ac727f271318acfa9a3af43575926aea84425f77ade9aed28d55e97bc96464d7f658f20db93c758e254e176dd6bbd23eba60b30a57c59cf36aad173253e8409bd279e9434b1e1fbc4a4e95bd715bf3c6958c66d9826198c9258f2b7e31e6e493f626cdfe78d7de2a1e991d21dcc439c33894746a79d5dab94d56b14db23ab65fea6ce8010f073a8b4b2a1181c1e28cb5871e8f251ddbd053a4327c36e287da214875f7d6f41ecf5536d64204c63085198f09951dced7850fe034f7affa7fa623cbef85335e979f999e5111eb18cf5a63fc3b45dec3e92ab2a5be6babc77e150f54fd64766751ded72b7cff24b3ba7a119b20d526c9eece9f64568683dcf756f860718ef99288db5a41c3ecb08121170633d75913345f17f6c0fd829bc7ef35641874a18f4edff504cd1f99bfa37826d5bc33bff609fbe3633c78f96fd9be5d737f5ee0103e739ff32d8f3b6fd89d56c4bdc4965611c32df4e43df2f50cfa64676dee31525d52c40c8c54bd87f278ce30263665fe39dfd5904b63fec23745dce766e3c00af4d4ec721d05f34fc8a358f7591cb3fc5ec3a6389f76e9c737eb71ccf1c4e6c9fadcc6d75cd73830552b7ddf9e3846cf63976aa50d5bcaed94c59aacb271a6a7dc46941ccbccfee60ad2914a28d8ac2226eb7c6fb6c82c6322b2bd4153be17382de6addb7e315e715ecb5854f378c0489dc49c87e92ab4d632df03dd29b90f465372b0d679ecb2bb32e7cf348ed459b37157c4f2271d7bbac4c89f50643c5df85dbe67797fafd45df5a1fa018d54cd53d19946bffff67e8a8f491bfc0ded40a7b6b23bf3ddf9de3dcc7f4bac2ae37ccd8f61b1f6badd5275dfdce894c5cdaaac54b1b0f2f5b92f633e03567b1c662bc51ee51c9b45ffebf89c649634c66073cff6ea27a00e33a4bac53e82efed1b7a3cf7f96cbe064e2fe8ca3dd6ed5e9b2cb1290d53a44e223bb8e765d85db976b02a7d3ec741634f253a1b994086bfc9b2e1bfe5d2670e340595e5a0d24fa027a6af670fde3828cbb3721fa1cef6b67460df79b634a4757bbd4f9949656cabce49b9ff7d647d930cfa937efddd242dda53675dd173fcb3f99b3ea7f2258138e7b7dbb11324f5d1b23e3ff273e7bbd73c2404e8b3b73b17b4e5ed4ee7a62f7e78bb337cec0c46bdfea8dbfd228a9d6e7730ecfccae5ce4dbf2bfe7d973b39e77fe572e7a6733368dcc30cc45ee77670fbeae5ce4da7dfad49f98ac557ef76de20fc77af76feff3c81c9af61ceaf8828abfd03399113202780d9e81f6f26daaf2f65ae2f65ae2f65ae2f65ae2f65ae2f65ae2f65ae2f65ae2f65ae2f65ae2f65ae2f65ae2f65ae2f65fed713f6177724759ede5687cfa671c0b64a3cedab58dc85deeeb5e98c40c975a1fa94df69f97a86a6c5bdf6b7a8c8812f31ecae32642c44b499116dbada17f79e7bdb7fe2796e4b913d67ddcc9b2f078a37c677d92dbe5bcb07b4a9f3789a4a52a4de6293e79e2707608c3190f4fe253decea5e95e7dcdc6360f4453b9379bba9c8b1b5d9cd3545ceaccd22343732994fe5d8ca7683075cdc9f156b36377a6fae0ea9a51cf83cf3f57858e77d89afa90971d6e3e1f7357fafc0d76ee33ae7afb16febdc02b1a70bc2bf9b2e44abca8bad3473731f4235090c29218e2e524d81cfb36cf7fa788a7ca34d171dd3e3b9315c7c33d0946523b7eb1e80846cc6f366bde3f7f835df3a85d25375cfaa29f2de5697d8c96417aabb98e7df26496c6d087af0e4c6383cc776a314fcc0ee6a77e75b7b5b5dd1b93ae938caa1682bee0ebf95393dc60bcfeddc6cd7bb625e8af97a33f83ceb88f5fda86a45b67afc5ee5918b3b7b7e87af0e9febfc5731c7f41e5bc16c6f37df56a8c3d4dae479418609d3389eec4cf68171a4e5bdbbdd5d90f2ad05502792b5963dcbd0536dba3858c682e76b2c4526ce54a6c0584448d5a9b3e4f98833fee60c83eb5fc04daeff135227199a2e3660b30ae71c4b073ccbee83f95a1681aaa7f5fb8331b6a5e3ee4cdf6c6e3dd7933615eb7b73e378b2d6726479f209a9cb146c1605bf4b86f5a8f83ed214b3e4835a463f402a0edfd09f0b55526229d2bef6f0bdf20e7f4ca7d938e6b6a18bf17bf9dc5c1e65ce3cc40dfe6873ec6d8ebdcb3532dcfe0a1e19ce4b3c36b0b7a0963149995fb38da1c430671a47aaa9ae0b2597bf7f80fe3029f9cddf88e8277eff2f1d8969f4b87c8bdc63ceeb24e13ab0a73c4f7758327f19dc874c9677f91ba768f31856eb028659f04c4e8fe57b83869e4b5c305b6fce59eaf4c2c69bf22ceeea798e81f93f0c8cdebbf269e6d2f3b9fa0fb6d4ff6efae16bebccfd45e5ab6afcc1eeca85c1eaa4f3771a2e9b9b6a8af6ac79857ff3e41a5fcb377d6504c5124367782cc618efeefc5c7f8ffe3065b23b93db8b71ebbe325f6177356eab2cc65cf8e7f20d856cab877cedc5781fea6bd9cc9db29859bdd161bec2b5a6977ecdca6c493c7f2b567fc36256f516ebcd9895eb8a58ca7858e5c32ef4c7dfd43d16ba2bdfe055f15cdfb1385fe4b2ea1859d887652ca387c3f99c964fa89d7ff70a964b7cb92eccdc6a3ce61b6712f39fb0c62ddb1b305cfafa09a8938365547189c7373ecfb7caf7870dba94ebe5659c6b8e55e5cef2dc75be9e79eed389a5eca812b0f816516068cd58ca716c6d5c51f727d95c9925a64152cddb450afee73f3fca63e5a90fe73f2df771fdf3df6b52e39ad4b82635ae498d6b52e39ad4b82635ae498d6b52e39ad4b82635ae498d6b52e37fe2cf7fff1b0000ffff010000ffff82a6a8f0654b0000`))) +var _ = pkger.Apply(mem.UnmarshalEmbed([]byte(`1f8b08000000000000ffec7b5b73a3b8b6f05fe9e2f5f3b431be2476d57e08f4186327e9899dc6865d535d429205410806816d32d5fffd2b898bb1934eb2e7f47938e7f8a11b6969595af7252d297f2b01dbc65c99fcad9020f373ef338ca32ef7d5143f77510c79cc18cec4f0972055264ad78f23dccdbc14001876f7711ab6b03a8a1525719afd01325f99bc396147b9071156264a0402a674942f3154268ad2511e414ac482672b91b8eb05ec6482651cbfc47b41d11dc8a0af4cfead7c56feec28ab0c50ac4cb234c7556789018f993251589c7d0a18cf00a5187df2f2ec13d88180028fe24f01fbe4e501459f20803e563a8a194f038ab9985730f099c44a474942829168fe590b42226c2341474b1824fe0dd2e0d08534381d88401a7a20c3bc2ba64adf1c14ff078c74231c9de29d8b5a209e48e43ddc143324170fe26e10e75940958e426322380499dfdd06148b8660f30b4e248f5ebe0d8408bc22c35ce928308e921473dedd5290e136803c0789ecb30c040ca75d1af0ac02e0836ca54592c54da30bca19cb0e0c125f1257f5517b107170ec6078da45da70d81bbf00740396e19401dac5680f52c4cfd1280d922c8047881f8156aff9790a18aa84753ec4732fa3f83810a1e1b1237ed7eac141abd36680fba077d2d386a393feb0a7b5fa674b66b425a7c3501d9ff6ba49181c948e82198c51c048abd9059cf5da7d0f703c1a9c400206d2a20d817cd7eefab83d79f749785cab9f481bc6691aa782ca2d0582808ffb0d89bd7cbb0534eefa38c5e76331a158fcf6e9350f7831dc05fc232804b3f7b08e865533f631f404a4fcdcf9df424f639282b320e003ee07304e13b1f83e05c9cf8649fc5b94d32c9034fe17a251434e0412fe41d40c843866dd57517d9c805f348d0858193e57c0ebe1f4432117471e46ff2c38ff048f67283ea32f893916e151044e8a33fcf6681746e87d8c6e95df7e71c2385ffaedacf21e22a7f9991a78b2edf5bb491516489c84e473c0ba0588e8e79d8879c29cab4f17a6b02f608d798894042869836092b7bbdb28e3719ab5410c67590a206ec3625e3bc9d1f76229cc560e38fd498ab714c38c06d90998078c50bca501f14f56e5058780d22e3e6088d9eeb5a19cc9387d347eccb33237cbfcfb3265472253979fae17906333e375bbca405110e1ead395312101522812f0571e67182569c032b123523a4a691b426b7e9625ada6fcaf965e03ac29ae6022d727692cf3bce8e7a918914e10732980b29597a13029b938dd7f74944abeb245f021691a5d5eb00c0849a539cb4ac6aa5617cacd5add43d893f656f71bc9822c8e64da7f315289f4059c1782fcca947896c258ea906769c0881c2a18ac3ec7e92bcd2a1d45c8a49be128a9f64c27fd322d0868c94dce0218a356ab9b67dbdee8b47f2dbb1c6c05de0e3314a75d1253c0c8e73825dd43b7de55f900fa40533f8695c4b4e8f5d5e13bd8f223bcefa378f54ee32de43cdde17a87f6069e1fa2eddb182f37676f20bfc3b13060c4b8f81761ce01f9d974272e4272e982efe225697c28de41d4ba7e0260f806568018f8c9302f7815125f1b9556c831cc53dcf50214a4f94fa5551a6c0a18dfc669f416526da362c28fe03131df9f1de511f3ac397bb19cd212d49cb64ad05d8c049193bf950f9d44efc411b43a137ee8946bc677317a07ad4be2cf518c24b68d531ec84366ef736fa0fcf8f1a3a36c4b1ede3c764f44f33711cebbf2944903ef781e17e776f1453803019553b1e391ba85db5178f08c958936babeee2891081a9381a6cae677194d268aa66aea6feaf037557bd4fa93e1d544533ff7fa236d341c5cf5fe9faa4d54111b02fe1d09f96c01e558462e5918c03b65321af57bbd8e62b15899f4afafc6d79a7add51ee69c04265d2932ac1caa4dfef091abe054899f45455ed28e6b1b9f9fe3d014855266a47592231a9da51562dd2751a969c0cd4f148746318726572dd516eb22012a4ac305426bde1f5b83750af06c38e72cf05647835d2faa3befaa3a3dc9d615e0f86a3d140ad311b9e7f7414e3e3a89befdf7396738c94c9bfd58eda51ff943a1627d50f554e1a3d9f97508e759216c6cb5ac9b11a72ac7894ae50153c2a8d9d563cda758c12fbcc8fcad3fdd1cdfe17785eb595100486e4a335aa9fbae18f8e824006948982f731b1ccc3ced1a6dc321fc81f2bfdd95d1fa2db9b78611937cc2ae0c8321f7268666c711313cbb8215e34cddc4795b906e44670432c43efc1683f9e6b2841a6df7382e193a7a93ba8913d8cecd1adb6dc39fdfb9d170d2932c6a1a7dd6568b34cbce3ef63777dd8ffb1395c6fab352c4327ae397ebaddd82a581f8835d30bafef26ae693f5be6ef64c5eaf9f4c4634b0a23d777b46fc48dc68565eaa1a7dd3f7bda3077374b72dbcc79439c95be80da38f7225bb57e9f3eac5602f78eb89a9dbb9bb98fcc7161cd96b1b3792068734f61a13fa3d9bce7f4973d18d9a1652e7d64fe4ec07af88c4cdb770d7de706fa93a7f532773d54613158c8b566352f2137482ce8f71d2df35deddbe86ba05f957cdf304b1b525440010b6f23375f182877379038fd3975364bb1be6f990247df3bebfbd459236acd849c097135aab9eb0171cca1efad6d726be804ac7b7baf3f571d43b7bf857be2ac87a16596f2750c5d7537be3a2ff664ad3de4ce66cec47aceba4705cc3586df6ed9bd0a239abb4528eca1c611fa65b7861eded2871cf69705580fd962257fe3c308c673cda7deece6258ead26b78dddd8fd85a4f51bb76676e1197b024c9b43236ccb845825cea8faf2066ea0c4dbe83bc81ed8d740dadc71cc94eb8faa6f6d5763b9f64c2d7560c02761c7f3e77864cdeea9673a3934a74f409b32d7e6952e10b20c2ef88ad07af874cba44d5b427e0b03491e1c434fa45e66f7b1903530a7b96be891d7b78873d4735ee2c292ff999ebaafadbb12360a1bfa3c6dfe97bbbe57e7cfb1a061e744713cef491f489049853dc875846fa0758f7a6c9978116cfbc3ce8912eaf497cf96e1135ce863c708f379114a99d4f37f7d8a89f47de386cc8b7b4103fb800d56733f70cb1cef9149775e34e5de2c2448a32a30f4d0dddc3fc188966381ee7bd10371234a3d73f97c6677d226dfb1bb08ac6deeceee1abbaae97f695752efe37901b965ba89bb3e84f0b977b55d95b66599e30044f613fa72e4bb8e3b5f83f27b8c137abdeea899abd8577ead92ed6abf68fcb72fedc3ace96cc9f01b30bf1167d61ebf2796d9dbb92615f3179639d6dccdbc006b296355e0d7b22ae39d4e215b0e2d731c59e634b7cc298791edbbb3b0d18ddbe8fb41c8c83bfa634d3794f2b4a52d49bc64b30a6bffe883cd325e566b2e1a5ecf6466f09135e38b3a86d6347e0d1a3935fe267d2db86964342fee850d0bb9947925aae69c7ed0dfb4f95f6ffa5b43a3f02344da762e6c1c6a3677838677e9934d6ed3dc9e17d53a1b072020cc1679c9a42a588b3ce3ee60207349cf5bdb3932857db6f287d9a3c8f477eeec8e2c4c31d7bd0ad6e31c163227347eb858553140e2dcbd1aff858edc6810cf655f7f127a4655ae13345badf96f0d7d0767cbc4d386be671e864d3e30ed67a73f4fcab14115fba785dbb7abdc04b935cdc2db685ab8056165ec6dec43e439114b12e96b158ef0c3960df990cd7dbca268b1aafc41c605bfccdf2b286cd087117afec3ced0e251c43b4ee6da917611d73ee07f728eaf41f9ade1db958ce7951dc22719737e2ff3ce4ffcceaaf25ee669cb44d82d0cf41d8aec02463474377704993ead720641a63d40653e1736a6b665feb1d8d5924b6bfd2a362532e61637cc6576eef4a58ed8e203f2a8f83ec9636e3468f994a4d3abe3f86675934a7b12eb1443e9e32ba96bc21cd3cddff62769a3a7b9cb74f3962f957e2a724dd1f8b8d053e9234669cbc2ff1606b2914939d82c1321eb726f765fb8eba92af6066df99ed969b5ee11f60ff395a4b5ce45471af70499d354d2305bc6ee4a977ba05ba38cc16846f7eeaacc5d5e5f97f439eb03c72b31ef92bad1b4e7cd1e088aa61cadbf9dc55db967797bafd45f0ea1f90e8ed6acd3e039ebe1f0e7fb2939276fd137f698cd3d233c89dde5de3d2ebfb5ad1a3725cf8f71c5fb11ee9a76e46c6c2ef266d3369a5cd8c4fa32968998019b3d8ef0956a8f726a9bd5f8ebf6392d5ced8680cd9dd8ab3f03735c20d3aff611726fdfd2e369cc17ebb5ecf40cafde635deface90371b4718ecc69e2b13bd986fda5efb1651df3a51db4f6542adee8140afb9b3eb4e2b75ec7cc9165a0bacd1afd303b7322bbf81adcb0ba3daff711e67ce7697bf1bbc0d3c6fc083fee53e65a9ddb9a7352197f1fc5d8b480d17478fcdd34afe0395e35f8d2fec5faed98d3c412a62e64713ac52c3b1e2d8fe74779ee7cb3ce4363803e5ade39c3adcb3bc3f1f0bdea4e4f7dd4b4c9e07ad21f7fee8ffbfdbea60eff4971e76aa00e7f5d714712fe1fd576fa57835e5d8619f747c3abab81aabd52dc91a8bd718d5ab3acbe52dc91a8fdd171d6f1b8af6ad7835f50dcf99ffd2c868bde278413cc1066b0987cfae98dfce551cce551cce551cce551cce551cce551cce551cce551cce551cce551cce551cce551cce551cce551cc7fdbd5fc5935e47823ef99e32767bd279e4903eb8b5a563d67f74fce7a10cbaaea633caaaa3bdcd3ee7dcfd07db421b1a71dc25b434fdd4d98585fd4a68207d60e81fd6578cbee55673357614fde16c440b387b7e50d7f22aba0eb41f31bcbd055c86c7abc5db539d4be1de78ae8f3637d13678e0340b3d4dd50b458e92a30edfc78637b43045dd6ec706d190f3914f406fece89124facbf5985c74ae3faf0ecaef49d673e1cab6bf25664cc5d731f3b1b7b705bc1ea5bc0fae6ea617de0928e20e496e9fadeec9e2eaa1b28cf1cfbaee1ef810693af4fd5ed41f3e261dac3862fd6e4969951bc0ac91f0f4d353e71831bff05bf333d757b6ff3fb53b9fd673a1074115ce8a5ac1e6302cd8cadb58c625b25db239d54f06b99346a6e974b39ef81866a39cb9b0e8f1c5f4258e698bb867e654d4b5ebe0637e16d74d839eb87b8b54e62cdd4d77554cd55cac7dd79e6922fa43cf715acaa5efe5edd2aaa53ee557ab835f4abed2ae40653af0c9270b0b6f24a4fa5ce37be6a47d36261a0cc59d37c5e848941fef5aff7aa9565790bff5fa86f5dfeeceb52e1ba54b82e15ae4b85eb52e1ba54b82e15ae4b85eb52e1ba54b82e15ae4b85eb52e1fae57ff6f5ff010000ffff010000ffffbb09ea736e450000`)))