From 1822101db795b63b4abe64265d03b6197c1a0da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:58:49 -0400 Subject: [PATCH] wip-devtools --- json_tree.go | 93 ++++++++ json_viewer.go | 212 ++++++++++++++++++ json_viewer_test.go | 119 ++++++++++ messages_devtools.go | 4 + .../snapshots/darwin/json_viewer-dark.png | Bin 0 -> 16370 bytes .../snapshots/darwin/json_viewer-light.png | Bin 0 -> 16084 bytes 6 files changed, 428 insertions(+) create mode 100644 json_tree.go create mode 100644 json_viewer.go create mode 100644 json_viewer_test.go create mode 100644 testdata/snapshots/darwin/json_viewer-dark.png create mode 100644 testdata/snapshots/darwin/json_viewer-light.png diff --git a/json_tree.go b/json_tree.go new file mode 100644 index 0000000..fd570f5 --- /dev/null +++ b/json_tree.go @@ -0,0 +1,93 @@ +package mujica + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "regexp" + "strconv" +) + +// jsonNode is one value of a parsed document, in document order. +type jsonNode struct { + key, path string + // value is a scalar's JSON text, or "{}" / "[]" for containers. + value string + depth int + parent int + // end is the index after this node's subtree; count is its child count. + end, count int +} + +func (n jsonNode) container() bool { return n.value == "{}" || n.value == "[]" } + +func (n jsonNode) label() string { + if n.key == "" { + return n.value + } + return n.key + ": " + n.value +} + +// jsonTree is a document flattened depth-first. +type jsonTree struct{ nodes []jsonNode } + +var jsonIdent = regexp.MustCompile(`^[A-Za-z_$][A-Za-z0-9_$]*$`) + +// parseJSONTree flattens data keeping key order; it fails on invalid or trailing JSON. +func parseJSONTree(data string) (jsonTree, error) { + dec := json.NewDecoder(bytes.NewReader([]byte(data))) + dec.UseNumber() + var t jsonTree + if err := jsonParseValue(dec, &t, "", "$", 0, -1); err != nil { + return jsonTree{}, err + } + if _, err := dec.Token(); err != io.EOF { + return jsonTree{}, fmt.Errorf("unexpected data after the value") + } + return t, nil +} + +func jsonParseValue(dec *json.Decoder, t *jsonTree, key, path string, depth, parent int) error { + tok, err := dec.Token() + if err != nil { + return err + } + at := len(t.nodes) + t.nodes = append(t.nodes, jsonNode{key: key, path: path, depth: depth, parent: parent}) + switch v := tok.(type) { + case json.Delim: + t.nodes[at].value = map[json.Delim]string{'{': "{}", '[': "[]"}[v] + for i := 0; dec.More(); i++ { + childKey, childPath := "", fmt.Sprintf("%s[%d]", path, i) + if v == '{' { + kt, err := dec.Token() + if err != nil { + return err + } + childKey = kt.(string) + childPath = path + "." + childKey + if !jsonIdent.MatchString(childKey) { + childPath = path + "[" + strconv.Quote(childKey) + "]" + } + } + if err := jsonParseValue(dec, t, childKey, childPath, depth+1, at); err != nil { + return err + } + t.nodes[at].count++ + } + if _, err := dec.Token(); err != nil { // the closing delimiter + return err + } + case string: + t.nodes[at].value = strconv.Quote(v) + case json.Number: + t.nodes[at].value = v.String() + case bool: + t.nodes[at].value = strconv.FormatBool(v) + case nil: + t.nodes[at].value = "null" + } + t.nodes[at].end = len(t.nodes) + return nil +} diff --git a/json_viewer.go b/json_viewer.go new file mode 100644 index 0000000..0a847c6 --- /dev/null +++ b/json_viewer.go @@ -0,0 +1,212 @@ +package mujica + +import ( + "fmt" + "log/slog" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// JsonViewerState is a JsonViewer's open nodes, search and choice. +type JsonViewerState struct { + // Query finds keys and values; matches open their ancestors. + Query string + // Current is the chosen match. + Current int + open map[string]bool + tree jsonTree + src string + err error + rows []int // visible node indexes + stale bool // rows need rebuilding + searched string + matches []int + sel int + list ui.ListState +} + +// JsonViewerOptions configure a JsonViewer. +type JsonViewerOptions struct { + // Height of the tree; zero means 360. + Height float32 + // Expanded opens every node at first. + Expanded bool +} + +// JsonViewerResult reports this frame's events. +type JsonViewerResult struct { + Element *ui.Element + // Copied is the path written to the clipboard, else "". + Copied string +} + +// JsonViewer shows JSON as a tree with folding, path copy and search, virtualized. +func JsonViewer(c *ui.Context, data string, s *JsonViewerState, opts JsonViewerOptions) JsonViewerResult { + if s == nil || opts.Height < 0 { + panic("mujica: JsonViewer needs a state and Height >= 0") + } + h := opts.Height + if h == 0 { + h = 360 + } + if s.open == nil || data != s.src { + s.src, s.open, s.stale, s.searched = data, map[string]bool{"$": true}, true, "\x00" + s.tree, s.err = parseJSONTree(data) + if opts.Expanded { + for _, n := range s.tree.nodes { + s.open[n.path] = true + } + } + } + u := Density(c).Unit() + var r JsonViewerResult + r.Element = codePanel(c).Height(h) + r.Element.Children(func() { + if s.err != nil { + ui.Row(c).Padding(u * 3).Gap(u * 2).TextColor(Tokens(c).Danger).Role(ui.RoleStatus).Children(func() { + ui.Icon(c, icons.Must("circle-alert")).Size(16, 16) + ui.Text(c, fmt.Sprintf(msg(c, "json_viewer.invalid"), s.err)) + }) + return + } + jsonViewerSearch(s) + codeHeader(c, func() { + moved := SearchInput(c, &s.Query, SearchInputOptions{Find: true, Matches: len(s.matches), Current: &s.Current}).Moved() + if moved && len(s.matches) > 0 { + s.sel = s.matches[s.Current] + s.stale = true + } + path := "" + if s.sel >= 0 && s.sel < len(s.tree.nodes) { + path = s.tree.nodes[s.sel].path + } + codeMono(c, path).Grow(1).MinWidth(0).SingleLine() + if Button(c, msg(c, "json_viewer.copy"), ButtonOptions{Variant: Ghost, Icon: icons.Must("copy"), Disabled: path == ""}).Clicked() && path != "" { + c.WriteClipboard(path) + r.Copied = path + slog.Debug("mujica: json_viewer copy path", "path", path) + } + }) + jsonViewerRows(s) + at := -1 + for i, n := range s.rows { + if n == s.sel { + at = i + } + } + was := at + s.list.Selected = &at + s.list.Label = func(i int) string { return s.tree.nodes[s.rows[i]].label() } + list := codeRows(c, &s.list, len(s.rows), func(i int) { jsonViewerRow(c, s, s.rows[i]) }) + if at != was && at >= 0 { + s.sel = s.rows[at] + } + if at >= 0 && at < len(s.rows) { + n := s.tree.nodes[s.rows[at]] + if n.container() && (list.Shortcut(0, ui.KeyRight) && !s.open[n.path] || list.Shortcut(0, ui.KeyLeft) && s.open[n.path] || list.Submitted()) { + s.open[n.path] = !s.open[n.path] + s.stale = true + } + } + }) + return r +} + +// jsonViewerSearch finds matches and opens their ancestors when the query changes. +func jsonViewerSearch(s *JsonViewerState) { + if s.searched == s.Query { + return + } + s.searched = s.Query + s.matches = s.matches[:0] + q := strings.ToLower(s.Query) + if q != "" { + for i, n := range s.tree.nodes { + if strings.Contains(strings.ToLower(n.key), q) || strings.Contains(strings.ToLower(n.value), q) { + s.matches = append(s.matches, i) + for p := n.parent; p >= 0; p = s.tree.nodes[p].parent { + s.open[s.tree.nodes[p].path] = true + } + } + } + } + s.Current = max(0, min(s.Current, len(s.matches)-1)) + if len(s.matches) > 0 { + s.sel = s.matches[s.Current] + } + s.stale = true +} + +// jsonViewerRows lists the visible nodes, skipping closed subtrees, when anything changed. +func jsonViewerRows(s *JsonViewerState) { + if !s.stale { + return + } + s.stale = false + s.rows = s.rows[:0] + for i := 0; i < len(s.tree.nodes); { + n := s.tree.nodes[i] + s.rows = append(s.rows, i) + if n.container() && !s.open[n.path] { + i = n.end + continue + } + i++ + } + if i := s.list.Selected; i != nil && *i >= 0 { + for at, n := range s.rows { + if n == s.sel { + s.list.ScrollIntoView(at) + } + } + } +} + +// jsonViewerRow shows one node: fold mark, key, and value or size. +func jsonViewerRow(c *ui.Context, s *JsonViewerState, i int) { + k := Tokens(c) + n := s.tree.nodes[i] + ui.Box(c).Width(float32(n.depth) * 16).Shrink(0) + mark := ui.Box(c).Size(theme.MinHitSize, theme.MinHitSize).Center().Shrink(0) + if n.container() { + name := "chevron-right" + if s.open[n.path] { + name = "chevron-down" + } + mark.Role(ui.RoleDisclosure).Label(n.path).Cursor(ui.CursorPointer) + mark.Children(func() { ui.Icon(c, icons.Must(name)).Size(14, 14).TextColor(k.TextMuted) }) + if mark.Clicked() { + s.open[n.path], s.stale = !s.open[n.path], true + } + } + var spans []ui.Span + if n.key != "" { + for _, sp := range codeMatchSpans(c, n.key, s.Query) { + sp.Color, sp.Weight = k.AccentText, max(sp.Weight, 600) + spans = append(spans, sp) + } + spans = append(spans, ui.Span{Text: ": ", Color: k.TextMuted}) + } + switch { + case n.container() && s.open[n.path]: + spans = append(spans, ui.Span{Text: n.value[:1], Color: k.TextMuted}) + case n.container(): + spans = append(spans, ui.Span{Text: fmt.Sprintf(msg(c, "json_viewer.items"), n.value[:1], n.count, n.value[1:]), Color: k.TextMuted}) + default: + tok, _ := internal.Highlight("json", n.value, false) + col := k.Text + if len(tok) > 0 { + col = codeTokenColor(k, tok[0].Kind) + } + for _, sp := range codeMatchSpans(c, n.value, s.Query) { + sp.Color = col + spans = append(spans, sp) + } + } + ui.RichText(c, spans...).Font(theme.MonoFont).FontSize(FontSize(c, codeSize)).SingleLine().Grow(1).MinWidth(0) +} diff --git a/json_viewer_test.go b/json_viewer_test.go new file mode 100644 index 0000000..8ba2c81 --- /dev/null +++ b/json_viewer_test.go @@ -0,0 +1,119 @@ +package mujica + +import ( + "fmt" + "strings" + "testing" + + "github.com/egoist/mygo/ui" +) + +const jsonSample = `{"ball":{"hall":"east wing","guests":[{"name":"Sakiko","seat":1},{"name":"Uika","seat":2}],"wine":null},"open":true,"two words":3.5}` + +func jsonViewerTester(data *string, s *JsonViewerState, opts JsonViewerOptions, copied *[]string) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if r := JsonViewer(c, *data, s, opts); r.Copied != "" { + *copied = append(*copied, r.Copied) + } + }, 640, 460) +} + +func TestJsonTreePaths(t *testing.T) { + tr, err := parseJSONTree(jsonSample) + if err != nil { + t.Fatal(err) + } + var paths []string + for _, n := range tr.nodes { + paths = append(paths, n.path) + } + want := `$ $.ball $.ball.hall $.ball.guests $.ball.guests[0] $.ball.guests[0].name $.ball.guests[0].seat $.ball.guests[1] $.ball.guests[1].name $.ball.guests[1].seat $.ball.wine $.open $["two words"]` + if strings.Join(paths, " ") != want { + t.Fatalf("paths %v", paths) + } + if tr.nodes[3].count != 2 || tr.nodes[3].end != 10 { + t.Fatalf("guests node %+v", tr.nodes[3]) + } + if _, err := parseJSONTree(`{"a":1} x`); err == nil { + t.Fatal("trailing data accepted") + } +} + +func TestJsonViewerFoldKeyboardCopy(t *testing.T) { + data := jsonSample + var s JsonViewerState + var copied []string + tt := jsonViewerTester(&data, &s, JsonViewerOptions{}, &copied) + if !tt.HasText("ball: { 3 items }") || tt.HasText(`hall: "east wing"`) { + t.Fatalf("closed: %q", tt.Texts()) + } + _ = tt.Click("$.ball") // its fold mark + if !tt.HasText(`hall: "east wing"`) || !tt.HasText("guests: [ 2 items ]") { + t.Fatalf("open: %q", tt.Texts()) + } + _ = tt.Click(`hall: "east wing"`) + _ = tt.Click("Copy path") + if len(copied) != 1 || copied[0] != "$.ball.hall" || tt.Clipboard() != "$.ball.hall" { + t.Fatalf("copy: %v", copied) + } + _ = tt.Click(`hall: "east wing"`) // back on the list + tt.Key(0, ui.KeyDown) // guests + tt.Key(0, ui.KeyRight) + if !tt.HasText("[0]: { 2 items }") && !tt.HasText("{ 2 items }") { + t.Fatalf("Right did not open guests: %q", tt.Texts()) + } + tt.Key(0, ui.KeyLeft) + if tt.HasText("{ 2 items }") { + t.Fatal("Left did not close guests") + } +} + +func TestJsonViewerSearchOpensMatches(t *testing.T) { + data := jsonSample + var s JsonViewerState + var copied []string + tt := jsonViewerTester(&data, &s, JsonViewerOptions{}, &copied) + _ = tt.Click("Find") + tt.Type("uika") + if !tt.HasText("1 of 1") || !tt.HasText("$.ball.guests[1].name") { + t.Fatalf("search: %q", tt.Texts()) + } + data = `{"broken": }` + tt.Frame() + found := false + for _, x := range tt.Texts() { + found = found || strings.HasPrefix(x, "Invalid JSON") + } + if !found { + t.Fatalf("parse error: %q", tt.Texts()) + } +} + +func TestJsonViewerLargeDocumentVirtualized(t *testing.T) { + var b strings.Builder + b.WriteString("[") + for i := range 100000 { + if i > 0 { + b.WriteString(",") + } + fmt.Fprintf(&b, "%d", i) + } + b.WriteString("]") + data := b.String() + var s JsonViewerState + var copied []string + tt := jsonViewerTester(&data, &s, JsonViewerOptions{Expanded: true}, &copied) + if n := len(tt.Texts()); n > 300 { + t.Fatalf("%d texts for 100000 items", n) + } +} + +func TestJsonViewerSnapshot(t *testing.T) { + snapshot(t, "json_viewer", 600, 360, func(c *ui.Context) { + var s JsonViewerState + ui.Column(c).Padding(16).Children(func() { + JsonViewer(c, jsonSample, &s, JsonViewerOptions{Expanded: true, Height: 320}) + }) + }) +} diff --git a/messages_devtools.go b/messages_devtools.go index ebfd5fb..abe4d0e 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -78,6 +78,10 @@ func init() { "trace_waterfall.detail": {"%s · starts at %s · takes %s", "%s · 起于 %s · 耗时 %s"}, "trace_waterfall.error": {"Error", "错误"}, + "json_viewer.invalid": {"Invalid JSON: %v", "JSON 无效:%v"}, + "json_viewer.copy": {"Copy path", "复制路径"}, + "json_viewer.items": {"%s %d items %s", "%s %d 项 %s"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/testdata/snapshots/darwin/json_viewer-dark.png b/testdata/snapshots/darwin/json_viewer-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..29937571e0324476caeabe9c3500942b8ca785fa GIT binary patch literal 16370 zcmdtJ1yG#J);3CnF!grc}b+2bVt9#A7;ct~?G0{lTkdTlt<=#lEAt620MnZaG z1$g>+=6N!16B3d{gPgSFJJ0m}MFU%PbMlWTF3IE1cnw1J=H1NQjAlcU%p-ZEkHL4; zJZ2r)(jGNrvlh>lo=dD9u%9dANq)7WLB%!CW_psV6WA@?@&nmY$x@(Bs7H8i(!aja zJx`5xFYOhEJ!Wvtmgun0_?d$gcx)4TmMM^;W?ZEpEiLU`@}?OHO#S@-)WXUNAUH8; z5c=-!Q`D7BY^FoVLqqeGfIUN*L%)?AfZ|t0)U`kL%8H{#AMk>`Ypd&HSQrxrcL|p; zYppG4z}sdboA@mdS@j>~Oi5BM-sHqfgDVWC_W~sHbw8|T^ z0MZy*?k08r!y`zYr^@7&bgELaxn=Oh>fjX;2uLUlX=rxiNsD3%3uG!pK_-eVMy#SI zeu*+cg1jnp0hED%{V9aCzigtBSey63{Vp8$jGq0PgDbMjWO3l>|6Dy2m=O6xgdCxQ z3aqI1r9A++|x zWq1kdYf3aUY<0RC%RBz1bGqy=rY~;4BT}v|O;_#89qB=~g|OMl$q(%8mxI^q;Sd|Y z``Qw0@8(=8axrnD$Z!=LRbwrKgX+Y^ll7HhlUFIJFl$)JMoZ|@=?#0BPX24zjV7_- z<(eTce-jJGI=S8Y7Zo1z~-8_^-18I52d*8`Noxw=c{Cc_vL)%CI zU7MYXP#|+eB@8ycPO%uU?I@fk1i>;{=>Z57r@tj{Y_a5vvKlyW;#n(;G<*|ez&Vux~1P< zt7F6HcJww-cysxv>g?4!E%@ZHLOd@~>{W)Kw>_&-rlG|B+j;390hiD$IyvXWWV^Pj z*5Tp%Y!855s{ZEM8f_1jA?*ETGCS`5`8ia@cG>-OKr6g+Odz;}w#|?0xTZdy{pJ~T zb6v__g%h8UXUq&y?y>2lr0<0kz$5hL<40#Id8D}8k+-%LgfcSt$fGpYu*>nvsl~Zi z%@Vz)!vejg7Tf8A4^7FneYI28V#T#n#f#pb$kBd*u8KZC`}7Ko>pZZ}sK|hf@s0~X z-*yx$!F@E?oRgH|uyoWSz~e5i=zb*)&_GSwe#*1Hw;N*WSyPz8&bhL+DfwBt{mpZMv(oitm3{aARMWQ zyoCi1eYr7vDm+xk*}s>iY;WJ~FF-P2V`JieWrvivi5>Xiak2VEthv&HXe1;!H^+?p z=w01WR&7jdW%{jjqvL}$`K@v6WS^B&nv3&embiawK{@0WYD5g!kGwbXh)M|vZ{eqm z^-T&Q=MikoY_fn8Tf1a7L*i|TaJt?zsLQt0lNCuk?oU)3Td_bBo-cWIq}wJgfu`3{ zKY%O@Fpt{_FAgYl40$uO*Q|1OKR(_$kQxX!^7E^QDM;de+i77Fr@3zRwS-D`qtjlu z3TbRG^q>>Th}$~YwHO~2bVXkPa`J1ZbiqFe%%T|Z_Y5V?rhIO zJ5?q!lmgf($*(%qmpJR#zJbW+4wW)5-=E1vYu?T_MiUIyxo3!ebs)_-P|y-S+2=Rk z&lwJxQQU0Zs!eBM2b`UrTpimsFFK!EUls+5-j^ix8&GA@9j9~}SMAY##a5vplA9l& zwyUnH%_JcwTs^q$`-!-${$vphLEN7tDU04R8;{TTV9N;~@_!O=pTe2b)nn0M_J&DN zP~1FJMh^mwj@maI)5$5mV7!P~ABw>`+Zx#4PBh;zsH$3LQ!tc{*1P*~5y2!W{lRN; zvVHA(mP=HR?8XYo)A22TAFOl$RvLABhMn@Gz-BpR*9EZw}fAy%<-Z) zu~WurYHH!~GG@5#`t_J|S}Dp6=e1{!6G{Tt?4UZ6T>@D3Sh?Y7 zD%F1L#^OYN<>;u-ukf(Tt{t8eMs}d0lG}B5#ymOrF={gu>%+$2u z>nXE<@uhap$1m-S5R6-HPQEiNDM?>DxSjrH?mC}5L2GLVeSxB!Uw5;9Z zb*ZE0zxF#GKjD#qJFNzO@P7jBlg~$a&P5VcB?EUgZ5th-nVlHl98DjHy5L}VEuOAt z;8ZGzP#mqfo3nC?s&&tK117=<0T)>lMqtv=^@^ZSz-%y=<;D5oQWfXuASIk+X$3VnqWm`I|WcMGC7v%j{B?Mo~Kg{@l*uHUnU0e80({Vw7D zDa|a#Kgo-#*C(}Ve-kD0@}cf{E6C3BY!a<-{WOz3K9tHGpL4E1*QC7y^s3PgE1^f`yRr(BwI#bf(SE+i@$BSM_ekxR3JZ0sXw2^feHsT|t=%<@KgTH;dhmuK)U9_(Fa6IbE)twpI1TQ| zNTZFhDl{#xf0%It!K5WIhqAb|*Vstf4A15C**Oj@bI0^Bq5TS|&h-zZ3J!c`nhU?a z*Ppy+-QL-G=$5F1UweWescAAI%Zt&*Gql*~+urE1!xtfQH-smdmI4cw&_dmb{e%4o zV`77BY}rhT*naaB!Dl^u=Zj@qQw8Q+7zWfzW%Ps-;;b)%sKXFR_{vwHMNu`G^}M{nyiag5K|}FS5oZ z<`OHm45!gAbHBm~WBdI~x>*~M;0<2nOU-^t1Nup?UC!JUo-aek=$`dRwKPWu_+(TU!3I5Rhv`pBot5Te+of|#O5k7Lvs4qxn&Tze>1H($Dhm&uR4Fz0G(@?tuBP!wURqZ@FdTNM& z!)Li8du8c0!W`}RYHF;wZ)%enKq7@{B&7HJjre^^dWMT9u|!gV7w{SO;M0-}Dh#D@timBnM2pr<6gzu$Z#fU9$M#%Pf+Ndi zAvX?Ei;E~R>4KN*!69bZ4GOqCwKvkjy}y$=I@ zP~=tJpp)EnZB%K!X11F=%?n2dg0-B_I>1j+zf6k#_->Cx%>B50I6+nUmQVNM#ZAk@ z=B;BArgIV|w;}PPG1mw6J_=V$HB-m;(){N{A^mv%)xce%?L2~hqr8m$p?%$Py&d@k zC(h1Ur#slC$GHk4_Z_ZkntrX{4qAya)JW^Td(>fvVYFc_uzL1{p`iDz#Fj#2{;}UW zEsaQ0#68qMFImOcarbs>i}B!%0h>BAM-ss>Rt9)+4BMcg-hJqfEj(6Wfq&t*Tek?0 z9FQ*jjq}Gd(Xby21`FH69$mCKfuAyK+TbaP0RbOO678*UDCkFaTpQpW*B3wg z0++mF|9gqKd|nLo;oXIItUMN;aqGh@J-n@LnP~?Hi!$g6dGIL{5Z_Y*?Ku+9S2fsl zUVvv-nc0|C&9i8(1Uj#RvBJ-=#B*6cMQ69=J0@;Fz+PvCg3OE%viV z0ghxL)BV<^9U4!xx~LGvNJu}v{#exwwV^0=i>#mee-qD7S}>8Tozn>Mv-oeflF!rF z+S(EiuW@scPhqsDN^0|1y|j7p{`9^cYg~rGm^5XACoG5G@*s>aQ^H zYkcmnxgc6=LNQDk$RD%IX@T@Vnb!ZT1M(L*;8>tHWP}z%2|5Sl$O6Q`+c+`#lpvSw z(3l3|El&I91SzN@JC6J|_utgWCI!LsV_^3Y)+l;MXvFs7-x3|SN`E=u#(<235A?hK zr22@ugokesF}$=W1$=Js;@^;1ZHeUE86^o9qakM?@;((yBTWO!Ib4Yv42}0!73G94 zQKGT(!xWZ~AEzY{`DfsJ$;irVIAi~L?fbP{o_NX(W0Qj0k3Y@mO5dcmI9@u$YIcq6 z2Up=~(Q$pjXtZMD)M34${Tm9UaE%?%u@cbTbQe8*u#q7zNcCq-e|{YKadb&J_QCDk z@G7Jv69K}(BK0} z9k=^~O5!yeA2P#ZGAJnt*zF0o4Eyr>|4@^{r=L|7c@oP6c=`)9&d4p3p%H1(#Na1} zhOSS9Q@p$cLr{|p`WpM@kYhO3D$%iB)ZQ<*Lj?|vh+1669xTc zcOx{?Ru2<}s{S38lFY(+8RxP*+2htu3j^w*@1ua-0OLQa5pWM6RPR&Ar)G8WAt(Lg z37}I#y@qoddx%ahrwzVEbo+PJFMMcKuaCT^wyA{sN}-cllM>u}ac?C71~2}=%)el? z`q>*2(D{}*vF9p4#R2rUD~|wUgUJG(JSO9mC;x`R{|~bG&!}#eg8a^qdE}ADL`(7e z8D35@`5KEOi29Ed!V!{RkP}vJkURGI`8j%y5#S!YO$2TO-UR1V zma-^XL|ZXKLupFjMvT8vZ&ie2bIHL{n{96qd%)g|WaGZa^%_SSx!r-c_*Vj|<~%hR z3I`SGgMTZ>9Xy3Sn1*MI`EOR!K@VmaBDOT`Sa;kBT;7_F-xFoOs>tSyJ;4$TxwnUp zm{gY4DK1@{+K8YAX7|7Pi|nmdY4&ke>8Ske{AlGKE+3>ZI(%DN9v9EsfuKNR?IR16 zwLJxd6u?WU!86LEKFwKb+9h2q#MtgYt#QSp+@wmx-1%RuxRik0(+WsY+^kX&iwkJ^ zJ#r=ScC4L~mCq}Vn#T2Z@5s7F*Sp83W3F%afH5=XJi`}uaJki%KJ>l*6VSPW>dM!5 z?H!dwv~rq;lj(A>OPz?wHtiv1=*haf?L1`lT4EXgYT*&DS)&fc8H{EsliqeS9FxqH}V<{(1daDJbSIL7%3gRZw79 zd0O3jD`3bA<3Foo`_LC`d9qd6(1v8mSeu}RzRl^lNt78m!67hhYl!i`vhA4?F2ObRT0 z?SEmm+B_R8qhixMd47gqA2I)%e;%XZZ9JypX!!`7t2n#At)>QVce8b5uSe@)o)t*X zIhEjmpHj6kT68v!Kl67^ieK_$n&4abCh>sP<9{twmc6~hn9PSD{vj`LEzTbQTm+_crSxakEPT}1pVnb z5FT6WOo@GcyK{kSW4*0Qt|Whhrk{)zJF?oU_|od)WPo*&+Yo977%1&Eu4LV~DQGhv z%7_B-f*;rG%hW}rmZqGK&|;8#P!$}#eB2~|owD|4YhgdwB`qWe{XXDZeH0r6v9GJu zn3@d288|oc*Tv(xYVeAb zci>;JZW+w3IlsH3;~zgpz&XwcX&`;6_VP`QFPLQkHMMf{c@%;%3ifqlmJbx)GBVhQ zlw(DCYfdy>s@^CPJ$q!Cwr87%Yp(-ZStwBn@TWD$^a!JCl9JR)4l^1%oze%*mbQ3I zLI`X5`C)2M&-w;Kjs~aSzg1kh9({y4&gNd0LA;8?BEf*T-j9~-?ZbQtQfh#S^yBtB z-ox$Q@@V>;@Uaiax*vtOQOrO8tj5G*nLqvIE&j)gw*ST4_P>i$|5Lw9SpdZFPz#ET zTc$A*VraJNiXd5`zAEtJ$NFqQ95rj)eRV1*!@B zMd7K|QMIIloeGl3kwm=A*)$R8^?>&FcJ}^ucAvBKp~`e=d^b>`onPh=_GR-HD~h(I z|83@3={8^#=PRwWY*@^3KqKurb^zT`QjZ_y$vQe9kb0dwB;eyh!t+~ZM6K;)W^n>p zp8|Z&pwhT&7b$pnMe4MDZh4+OMr67Nc(K6iR~2JAJ5vA41x5K};{-!yoV*7oW*yyB z5u7iaw=!zzr*iRt=W90+t{9QbLZDU5E9W(yHZ+meqAsl?dqzY1GiuEBln($D6)jTC#8SL?bnQ{xlp z0~R%|j!0WMlj#QJl{xy7;eD1I>=^Hm&&>bU$B{@onUp&Io=oWZ?5Hz1jG2>ZV0byL z(GvdB?_~t{Wt3Ezlq&v~AERu866LCAABDf^KFM~~6qU%ZgV%|R+JuLL)DsZ(bI3;^ zFop*rt~M*e!KM@)Gr2uiOaAhgVNl$&yQ zT<^m(LJhDAnq~>Oy*N0+l%p*nPY-6hPceNc_(ecwtGipzXoHAfbM6H<>_k=qKuw79 zn!52{S(e)I&gzpcYISWFy}zTreJ9%Td)V|2Zc`a0<2bw?%r8t793Dpn8QnYSdDv{N zNOAAM8}Od`qRMm)gdOpmYaWz*hdEwFMrjKk$*R0o$=a_L+GV!Jtx~@%TO1vo=*~jx z;3iJ~S&k86&dGYV5@NntTmbJUC`jW~r+Eh>WIe^`zpLJ!O@Fyd3$=#TM59{{_=0#s zC4491!}ev#jG8sq#R+4B zkl$kd=NUkSA}$ww-xf~WirlKCY7TC2_450vDJv}B^No`ObMa5|ug?!e_*2S6K`c6S z1W68a)63(~tG@Lp?KgmwmU>Qm3pG2P$s&$e2D4(nr%B+7umD_P@L!Pf&89GJ8ZEcI z7Y6@I)k;>0j6l7@w&EdOny-s|G|>JHmIMhx=%euGlD(7P z8v7>w4f^xOkfu9!gF%YS{;K?+ETvH+_ru!7pKFcmNxUr`8_bE*lb>A4=z%#oBD_=w zB25g>yS-Fjc_OHr-CnEX~r zeBGmwi0yc{-KQy^k4vO$!Mst;&8xdzF=oSBUinl+7ALKX5SNVW`ZJ_k24g;y8sL1( zd2PDfCUJ=wX|4kfQ_KHCLR6+Hqw-n4#C?8+QY|qVl)dvduSwn4`E^LghN!;BkXgk- zsWKp3P3ErrS_k~5?q}hLF)WdU=^_ck+imq(ifFqV3xjT+1`yXORoNZS>O#3ZzS=T% z+sc>YtQ z45VyMo2w&bhb~=-ty1F^Kktw(5P5bv7Y)dk0vrRrIfJvu>}&J}35V~7!al&-ND(s= zQI^g=7h;ce1n2&fEuXfdpyi~+k%=4paJI+Rjv;dt6lcGkj!*Y0BV_$<)8aii!j>I+ zQ;-xaLu0Y+rlWfN9Q0dF57ukaV2}Yc;Y{^bY~fYfYr?!skz{%TB4ni$BGL7yooz+7nryZkBL;a!a2&S~QwH?cVn#8Yf=JQ~T9+aDH2|kZJ zF=(K6Juz(f6**8P;I6f3y;kB&Xp6fN%c`J32j4QaM6CNu_)g03Ac-hMw4!g&cO&Lh zy(zYwwWnggBw#6GkY0_->*lL1v}5-8LIzR(>H*`?Z+f5+km9#XTr!iWtJ{=WH4Ec6 z=vk!_at(OEB}}@-9?1g-9c3UnO+1}3IpPsTfTM5H;C3hWf=CXG$gNz6X{)YCaZ+Mh zU)UG)P?*lN+r>h)kV4i>y)~vi+FCxfJ({?!0P^7c45pQrCYEI!+7!a%VowXhGkUUV7}ARQviFat<}V?+iY<9&s}O z`$6UhY3!uu&DfShX{=%dVSi^$=n!vuTaDK=E1PJsb z@W$_I|FhbxYSOWg$xK-nbSDrI zjM0m;w9u-X`F53lu_CCvQgA)J>gC^evJOUUm3)aS>2E>pJIzh=2$S$rc;UIbFFQ0JRHcLmXdDdy=Ya^ zutAS_o{5n4*;C<2ukl%cIS-%|Q;vS?rd**|Xk4U1y9xTq7@)(mcz4Z6lc9MwppGJ# z8E`ogi}HeEKY<1^ViJ{gwS28e6fPY@SysZtHd|2=5m{%dry=v9cXYj`F8_W0>)Rk% zj}#~=dmS%}d1WZebgJtOzA8TQ&u`;<4-b5R+A)+O205XmN^ zeJAbKWr@n&O%{09O+<0_f<5gg3iOsqS4~V z`jBGmVlM_n>d1e@wX~HTHyT&9uET%-4kmKs^dsZv&g7^%o| zA)lUIII1b`O zx+1$Dm!09J(%;_1z1(o#Du;$OzBn*~-RmSP8yUYWG;G`H9mH;%o#AA@*P6tad|$x3 zcgL4x{&d)=)m!Q2mk81yT=x&!G6niOKgE;HtzjXLwr4LHcw4YHVJ&s%PA~hL_)<6Y zg~RZWSiPH%cX6?!j;1ZYDku4amWqc{j29x=j_UI2pz+FuRAY`P;5>bL%g6X?$v1bW z(`fisN|I*R1uGh6;UW^g-H&a{`{9Ce`B(7Xg*0!+-A2RgWUQFHbPUK+K@a7&8~~~& z#+xYSbr&zF#O&WM1-S=X>{w8b9F*%}bo=HObu2CO{Qfn?A?C7?vHEd&{fr>Bl7$WV zE4BWSt$-!*gX4xl+49x!Va$A`^ArKWvVo@_HMy|wrE6pfG)(vQ_B1+wFfYktqs+F_ zeX{u^m9{mF_eqXwE(@=D2@A8~hdv=c_}IW%?&zpUzh)?SRRVEP@`_V%hbN%fXX+i= z(tenDP6bd{Kl7RIjC5T>99|yNTVfC-z7$u%*kGN{Jy1qk90uc_98%soENRAiyfY##6!@k1sQgH81h>|hjo?E1g2P{h4 z`DfKGa~9<&8KWw9Go?^t?!{t1JQOXRt29;GIE6hE2>?OF?~zs!gkTy{)N2kt0Yhth zvuBTI6je_Z!0*N+tG^krnmcmVy=*y2MYikm# zCTlC%qrf1>=gl%*K;^GML6jHE*`VJbd$cWbXI$}R%T+(YTgJBKt0y3UBmfKyEawG2 zTubfst|N%}1soQdF(1zXA=J-T&w=>g-s=!NH?9EYs})tmt>*mvB!<2&NJ&ag zOh%%d7m->!zJr~+?RNe2FA-=!it%P9*7*fIRW_@ofnpgc;YBHQa_wc8&|wMK;SIdt zFlL~NelpwoEngw&JIrJl=>$&lcUpbJ-e?u&EMX^G4~G52Ur{Q`9A&@T5my-jh@IQ2 ztGWV(;WEP}%0p@CZ~E|+@mLAefHtjGyKCmU8yeqU<!Iz|JKzAzLo@f^nV^}J?Vy(k4a#nWYQzA> z%LyNmwxZ$1p{xiWaU$;#N`Lmx*ZgyG|Ip=sLz6zR{4(vP?e#5e&!#>2jjC5i>mm}L z-;xtjz^TeFbW}RkSHREI)K8*5QkS981b!?eu!gx>UxR+rMc8i)!N89 z)WF=V{pLg5ZTxc8l5PUsyQV<|IMTqUAk;q+{0Qv1|4prLatRaePo>gv1V?!N%J87_ zM{J?~xl|Na256+5;DPV_0`I?8;aLp|;d2@DK+OjYQ(%f3PJ-mR{;o6@GDP?G6!+GS zi7$({_aP%B^19SC*jJMdhyj7lEW!|#_wUNNHM)q@*$ zqcbVXaX!1>tH52FY_wYz2a%~#%N(puWpjtISr~q$Vc5TJ3f-oN4SU6Uhn3DwRW?>@ zOCvIdm_ zMa&{4O=ZUQ%1*(!SJhc&C`#xxIzD@C-&>Aj)h`XnS_Xv#F?c+bT8*?6H-K$_LcSs57)ou&NXlVxiRp)IYxmKw$vW?uXFs=a-C#rbT$)KycwI|&=!#eo@;T9x8@$%uHlt_g_uJRj z-o&$SLK1Bgp94!FqIl+{NOc|3uzFf@dL=IIk=;J7(7i6PCLm;9%v|@a{BoZU8mL-j zYDr+uH_hu?d!NN>oaGG{iak;DX8U(&j(me6XHNyD-1HEnmTZowZq(+JTV+Y8+i`QC z5bETN8nlNtMkaL7sI}+H#|D4_PUZWXZ3kX9UAp0K&Ub$<_6XR;HAXIaHk0J6l1fcy z_Mw5_l*g7?$D0Jc%Rh9sPphe}ZMhusA;&7HD1!g|y3FN{SX(r#N}{(tXxOWMExzpa zZhDS{hr@U`U9d&hXUEZ8X@da#YxbUT_)wyE<5AtJK2k6m;eCk$4s~Q9FAhTHuN_!+ zi;Lbq?|1A=a`bG7cbf~HH>-c0e~AnodY6>pojqrF2qWy> z!rdA(=$o^H&~UWPA-7-sYAl1Fz&+((o?Dg-B5=r8$*KVOu>Tgu@bX$z0Q~EGW#nrL ziT|n7Gkf2cJVvH&+4>}b#IiarMezUgPhh+ZNMACa|F4&`NI?eSp;#jVCc;Eyr{XO4BJt}-r zn6@Pdun-J7!5Q+8*9=j5=Z&ENfL;x{7ilwmX!V+|F(`J}fODAu6)6W z>0S9UFeA$F43I?*n01f@5u#07Zq<>Uv&h%g>3EkcQ4ETX`$cBl5_i;>Ofj*F2-&n`O)$OA*v%~n#@?)(m z{~;;f0k|S;P;-0%e^-mHK%Q5;1XBmm-EFX z$yOa3BIF+B6Rj8YVq$5)HSf9JN@*q>Ilte<1D=R5YVS~&nG$|Gg`SEr?urFkQw_>{ z?cg_uTl5(#zP!7u^a;0K_eVSGN(qlj;<3A66Y4?+hgbO@y$(_-H+t0Ado#!GGcVTu z+Ldw)tp0yi19&dl-N>0iZla$1J2y=Qbypk&J=)1^Lqk-I$-ExcdW z656t9p1F68`*Cb2^)F(qp5Y#7>w}?Os_^|yhF%bi7y%73CKivi+&Q2SuZ`Rfc=`L9u~x1TUYjYft?I=kk@K0I8ao~NSvJ;cA8l*; z#lgc}26h}RfTSeG)9!1q(y`|=Xn{m2gi)T%1tua)cJaTyG{ZS$bA4)JA*#qbN znr@x88hrCbJs#8}Ev+ta5iS;qA4OqSO)@u~rAHo4iLv!2m zV7WDQ3avq$X^N@&AA~bzZR4wY$u6F2`fYhkH-&C|jQ;$ueEiY@vJ<|(tbG&sDIuWE zH+x5siJTC;&3B-oH<~?v0b7@a0KGntY*aaqqZU1Z5FY@&UD&Q0FALSVD z<=7tbcebvhD%IpYj`j8hZ?+U^CFk} z6y;l+I^fNx2 M9%Z7qID>^B9p|KG{Q6HO4I~DYm5T;Av>!AV=WaY=4XsbE??Rn5=4*dd`AOX59tUg$WZ6Y@kL4#nHT@B%^wnDR#mE;9>0z)|rirP>8Jj?xTz_f74y7C;+b_Z4oTD%$&T%-g!QTILG0w)|(X57{{tD^|rU+*;dtWr6EpM!iJ z0fig>i-qRv2X!z1)f5Gy-vma{;#5Y_%FUuzIuqT#tz1rJF>!BxKS5JoppKQ6<`GR& zaSri~mzF-)e)1Ow{K*9Wo%QNF$lC;zROh^Wx`Kkd#)ZSBGV555;{gFY z0?YG?jD^}xw|mb92L~N3Wv%{|JrrjCT31zw*{|fRPzX5;A-&r}fvea{* zZ?{7hnXe4$$o-HSf|%3OC{V67Y8e|%z2`UQv-)S&E}qP$Sj{;ASZ+E$lcL3@T3CKy z*tZ(Q!iXLi*l1tATF98MvtO#ebhEzMfK9I|$Gu!W4VjgU(711UG}KZM{{+5X%p-+acU`#)4MzI^RKN2%EzB`Tx8XG0vvGuO9C_h h58)+B8c5pbV9! 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