From 5101f60aaaf6a8285464a8079e222dacd5ff075b Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Mon, 25 Apr 2022 02:44:39 +0000 Subject: [PATCH] init commit --- .devcontainer/Dockerfile | 9 + .devcontainer/devcontainer.json | 58 ++ .gitattributes | 4 + .github/ISSUE_TEMPLATE/bug.md | 30 + .github/ISSUE_TEMPLATE/feature.md | 20 + .github/ISSUE_TEMPLATE/refactor.md | 20 + .github/logo.svg | 73 +++ .github/release-body.md | 0 .github/screenshot_01.jpg | Bin 0 -> 183491 bytes .../workflows/create_release_and_build.yml | 88 +++ .gitignore | 3 + CHANGELOG.md | 1 + Cargo.toml | 33 + LICENSE | 21 + README.md | 91 +++ create_release.sh | 248 ++++++++ src/app_data/container_state.rs | 425 +++++++++++++ src/app_data/mod.rs | 397 ++++++++++++ src/app_error.rs | 45 ++ src/docker_data/mod.rs | 277 ++++++++ src/input_handler/message.rs | 7 + src/input_handler/mod.rs | 262 ++++++++ src/main.rs | 76 +++ src/parse_args/mod.rs | 50 ++ src/ui/color_match.rs | 77 +++ src/ui/draw_blocks.rs | 598 ++++++++++++++++++ src/ui/gui_state.rs | 161 +++++ src/ui/mod.rs | 215 +++++++ 28 files changed, 3289 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/feature.md create mode 100644 .github/ISSUE_TEMPLATE/refactor.md create mode 100644 .github/logo.svg create mode 100644 .github/release-body.md create mode 100644 .github/screenshot_01.jpg create mode 100644 .github/workflows/create_release_and_build.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100755 create_release.sh create mode 100644 src/app_data/container_state.rs create mode 100644 src/app_data/mod.rs create mode 100644 src/app_error.rs create mode 100644 src/docker_data/mod.rs create mode 100644 src/input_handler/message.rs create mode 100644 src/input_handler/mod.rs create mode 100644 src/main.rs create mode 100644 src/parse_args/mod.rs create mode 100644 src/ui/color_match.rs create mode 100644 src/ui/draw_blocks.rs create mode 100644 src/ui/gui_state.rs create mode 100644 src/ui/mod.rs diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..755ba60 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,9 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.217.4/containers/rust/.devcontainer/base.Dockerfile + +# [Choice] Debian OS version (use bullseye on local arm64/Apple Silicon): buster, bullseye +ARG VARIANT="buster" +FROM mcr.microsoft.com/vscode/devcontainers/rust:0-${VARIANT} + +RUN printf "alias cls='clear'\nalias ll='ls -l --human-readable --color=auto --group-directories-first --classify --time-style=long-iso -all'" >> /etc/bash.bashrc + +RUN apt-get update && apt-get -y install upx-ucl diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..ec4c2ab --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,58 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.217.4/containers/rust +{ + "name": "Rust", + "build": { + "dockerfile": "Dockerfile", + "args": { + // Use the VARIANT arg to pick a Debian OS version: buster, bullseye + // Use bullseye when on local on arm64/Apple Silicon. + "VARIANT": "bullseye" + } + }, + "runArgs": [ + "--cap-add=SYS_PTRACE", + "--security-opt", + "seccomp=unconfined", + ], + + "mounts": [ + "source=/etc/timezone,target=/etc/timezone,type=bind,readonly", + "source=/ramdrive,target=/ramdrive,type=bind", + "source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,readonly", + "source=${localEnv:HOME}/.cargo/bin/cargo-watch,target=/usr/local/cargo/bin/cargo-watch,type=bind,readonly", + "source=${localEnv:HOME}/.cargo/bin/cross,target=/usr/local/cargo/bin/cross,type=bind,readonly", + ], + + // Set *default* container specific settings.json values on container create. + "settings": { + "lldb.executable": "/usr/bin/lldb", + // VS Code don't watch files under ./target + "files.watcherExclude": { + "**/target/**": true + }, + "rust-analyzer.checkOnSave.command": "clippy" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "vadimcn.vscode-lldb", + "mutantdino.resourcemonitor", + "matklad.rust-analyzer", + "tamasfe.even-better-toml", + "serayuzgur.crates", + "christian-kohler.path-intellisense", + "timonwong.shellcheck", + "ms-vscode.live-server", + "rangav.vscode-thunder-client", + "bmuskalla.vscode-tldr" + ], + + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode", + "features": { + "docker-in-docker": "latest", + "git": "os-provided" + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8d49f8e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.sh eol=lf +*.md eol=CRLF +*.txt eol=lf + diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..57f1613 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] " +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. windows 11] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..82ee01a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,20 @@ +--- +name: New Feature +about: Suggest an idea for this project +title: "[NEW FEATURE] " +labels: 'new feature' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/refactor.md b/.github/ISSUE_TEMPLATE/refactor.md new file mode 100644 index 0000000..5a279bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/refactor.md @@ -0,0 +1,20 @@ +--- +name: Refactor +about: Refactor a component +title: "[REFACTOR] " +labels: 'refactor' +assignees: '' + +--- + +**Component to refactor.** +What component(s) needs to be refactored? + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/logo.svg b/.github/logo.svg new file mode 100644 index 0000000..9f30c44 --- /dev/null +++ b/.github/logo.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + diff --git a/.github/release-body.md b/.github/release-body.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/screenshot_01.jpg b/.github/screenshot_01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fd08e93d0cb815909ec4ece34fb7951516457f77 GIT binary patch literal 183491 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ξH@G;l)Y1XoRcX6VNMsc?k(XYH-f6?z!Al_OW30In|7@{yJryNcL zCq!3{Fyvn2s2HZZqkJEu=mi;@wCAK0OxRbX1V_nPTxet;3<(|{y(X-OWoB2k@2(N zte=n1pJd_^{da9Px`=&KeK+z5Q~9tm>02%Jpnh%W7K&w8n;`(?B4F4w5W31@Li+nL!V+e;smJ$mKC`*ef zE{4sMXfz_%n4{G#riIW4FiTCL4tLL)1BEo%7nHsF#)#@Z6&5c^1Q?bXof>{Nf!q}s zS3-$jJX`m^Co`{v63*utH`*Xrt2*Y#K)(mMY6>n2gITcU;&vIO?tLTO;Ovwf zdk(jU1{ZyZ0IW8Wz!)q_?QML^im)b@e5k_4C1^e3Ri?jOsO1_~C*D7m^C}Zs1?-$Z3>3QSTyNe;3SLUPN zX?HSj8gUT4an{H=ck46Xei)6OO4UliZF%OFBCPHd@(6RfmyZh3_z(zLq<(l2|M5Ge z*_4eEKgzh>59;PQ7{$Vh_nO2KbELZ?%*sc$!jn=e(7iQpt;OqAPwpMSDy)r|RFFj` z<7pN41kQ5vd06gxg?>vzN_qB)8XwK{hGO|fa%~!Ujlmt)8U}XpN-o!0o2W7+JuqrN zLAQYd9i6$V`fz@P7D(gz=I3*Xr^7Z#Jb_+PmHqC}g+UC@c&Ub7I1|~;M8gT;mjK1b zpman(OEtbA32zF69V%7=eBfmfCuH>TnSA z`9&;h9$Ks~C{^ZB7b)=sfmYWj>5~Gj=C_AH9=w9f-5xe0TU*{qVg2o<5cK&73xGuz z(@76A5G!Jx$WEwr_9~uyk>hLngZjxrc}Dx>)`fWebs(+iAnux@b41aqh6-MxrNK(3 z$cOYLh<8DlUFh!h(+-z@?)rZ8&Cxg;VT)(i#Bx1K)EI;p*OBj1Xm+egihvU>yp`w| zN7Kq`8ToG=+NUf<<<_AUYn{{ed2%OVUuA1op;;d1Ohzj+xPrPsZE+bx+!-D{M(r4v zAzI_Rp~4!UBf6$$@8w>*&&vN4gfRJ(`))S3&KUovZ=jI;{~Vtn3GKE61+CoG4b?-+jlfG_qikn z;64CU3zX7c6S}VE)2|0vB-3fK4q3Yo0 zhydtI2ghN_!@pi3ZznX~I;xjL^-c2Y1kUa5pK4Z>Fn>EpTwdPg@C>RtC%J6SM~Pys z1jH8dRgsgE_k?p4j#V_7t1I3POC=4q0J%6iQ+WBG~Q-6WDp z;ic_px<=jv7^PUjk2O<8U6h)AiigRIdm_t@l?}B#?LfA0Y-`J&A+cnSlwra-~k}>txF^V@dOYbZ0OEh@3c|XGF z#FmyynP2`{mATbrTITo-Gf$rgMcdjEK8U{RqFmlo6dbqk) z8>Q5wI>4ngsm<2eSTCf(cQ2NkAiR(7eSM)ml!0JnAneD}s&LQo2vd@lBtIs4sx(3C zq{7LOqIyT$kA7DR>RHfD)tVD|Lt)}jx@r}X$s`NxU>1S3v{ugEPu9sVqnVjolTSB< z&7>^mX$C1AKj&m{{*@~vDTc=6xkyZYdgpFx+FY7ughjfd+sQ3FR0Si@jPsDXd*#@7 zjqcX?$o?0UBTyVSESd~eUd;t7$jDQb9VZ$`v3;Q1AQt@(i!Cm<#Nd3ChDIrA>$YXG z#s^Vg=$pvd(bKR068nYed-Ov{U7+@t$Z#;>&qh)|P|E@rGlhdS>>CNc;LSE;cg5Be zq%CID`jq~r6nXoU_`&#@5jGO-EGz)Xyr3oULvT9yT>asNBVRez?EZ(*+z1bvEd|mL zcyw!Jfvg;=*L;Ccg@D19jk5^<2vfMuKopMg;!ItNX4Y*WZQB}dj;Bp<{umUlvT;2h z7FQbc3z4-YT8mdxF<@(21Ai&3v9jn0jkBtlcoTB1B*{KCwUL~%6dI+xkQ~!XG>hb! zAZgz;n4y3g z$(|wIcDTHBJ@HeJ*RJwJkRq+Y7a0lDur?7X#_Ok*t;$Ln#W($!WU0_G;ZeMaPiKy} zS~Y&6ZY_Y9fW=uTpTi>2&SdORkI&LsDPWjyyI@)Y{waN z^(;CLVv|CAZKWP-UgA?FDN6%9-o!b5saoU*dU~wTuflqLqKW3%!}vSyz$WK(qM8f{ z<$}$WF+xaiqfQ4W>L~qRAo`hID!5& z_RQUBu}+eeVg8?=MimY7a4O)PN>vkKIxj0}seabNIW0Dp*$onxAiR5o0a!<=ms_B4 zYgI?7SJ)hbL{vJZh{>|m{Z7t+a;SO9szgHcDHKNX9vDR+fbVLMgh^{76OIeHY6d;D z;PQnaoew`v2o%Upd_7CBfx_Epj=L}a0L<|E@-s+(aZO` ztMy``rt+%*zgnrvQMh_d@g`Ot;byu|EJ|&Yj2dp;`-#XEVcBQ9M{pjdwi%pjgiJAn z+KWny^vB>g)tj;?(UD!_C#i3~rRtc2=v@Ng)0u3~MK`gbj*@6k;L_EpeGfN-XiKE? zvrZT20Zupq(`${7Fgb-O%EdR^-TiY-4jTHk6tAXI^(4B4OfWdmW*=dqULVO{+-w$@ zE9kZ#Q2mw>m+_EPL$GtJ|Gjb}`Eo+O;N0%@cz85cMlOq;wOq_ZS;|kz->s?hXf@Bp zr~NfBWJq)z@_dy4t{teoV~_xxD}PshDQ%igyfi%rNqA_0>)CCOhaa3sTK`H z8A`^4ti?a;o#7s4)^jpo1N*JCeG>ZLh~z{4KW!>NY0RYd>?L)&jFFw&M!-KVa0r6m z?Gkf7?5}XPIz3Exmsu`c%7^qA1tABG1 z6KnOu`kAoAi{%GIj>WTC^4rB}3ey$rc+mQPx}6dy|6GIhBcN<;FohVp^7idrLA12l zQKq-PRmbIjHn#NvI$TaPRSa@0qT>(Fhm{W3)S!5RRzb)6&qnJv;}m*7 z+OxmqN5#Rg$h^B2>5FjCqb z`I#z!Sf>{BRkRX_YyUW%Np<&wHk57)dZ5^A z*A3JH*~Qar`NmyJ4@Bk6#H^SETVkgReFNwk>zE7|Cbf(8)D#FW*oSPzXzWJp^>Bj7 z^9NPuNhdwo5pI{3VxIw2; z7s~9Il#ZsM&OZd=P~g=%?t(#FPcT+A9-^}mp$jsy*oNO*WVUd-G|r4x`U%KGS7k@U z>el8LeM9Rf3C04qUl_I6DM!b@lrLVLI-qxti-sClWZ<ssf29ijki0wI?C)1{Ev8KG`sxsd->1^6lVp>ES12?RAH)+0Y94~h5D4~!7xv| zkO4U?cP1C#*bGt?9KXIn#l#nT;J7mTQ?ccPLVQZijk&)Cvih;9tZ*vT5?qK=?SM*z zP&7uP#M~&tAx{ySI4K_Q_%pVZFImwkX94Vg!)X<*9aXVo)0W6!ddbNmsBjAV4@9ZL z+|OzQ8Zp&MD)??4kpa810Ay`y28^G9uB<&x`yS4F>SQV9#_O9BMdZ@MPX)M zb`wfb)_tn4eQ(-Ft{Inq(2p9J7`PCmoMH<#r{-tr=0;<49jj@3k6iQ3pdMe&}=13N-uWG?^E1r-{ zs^wyU49Gc}%-zZ!VY~-=R;QNkwR#wpz=ed+xo~_{*m(yKs#=In+4I@Vn9p%yQMN7D zXacXvx=M~enGd6{a2vHaN@C3n%Bdq#YqR-(e0S}2`%q7-2@D(k&6bX84ppv)l%ZL+ zKEjZn;GD`$i|7$FD)92)CPc$0PNe}$G*&;t zlo5xY_b^MeR>$DhfH_U=p`joaGN|KpO645*OXZLmSTqXXo%4;e&qUKT>Jk29Znu`*ebd)!eg2_ zrChwazCX_X(9B#)6!>t2D_kX~|lSS%%EjYB0@&_QZ2{ zetdM5(qw%-Oco(4_l7Ku4h3FuNiUp_W;4_n7vU{FGztmD<4l|3X518Js~ft}<(kHX zQ>Qz{W&=|zY~*42`=P1fQCL_#eDSV#p4a!(DcXQDY=39yq6Gag4UEv_jOtygu)hpU zisaA7e|huc7vN&3rR$%((ZVg7X&jx9oxKkEXXu1{Y1#xtE*#lty2&WKwRt?2W9}Pc zLTt~xxrnzc&(7EI$(ISQjVv(5TQqa=DFtWdK_{2R0xDafe`Ab-|AYV37G%bfKSMc! zx}i}&U7_oRU7$Z>Z>t5sTNvZ8uVfSTMIL4-3Ii2(5II?B>R_cg6*PWglvVa*ot^_4 zROxORx9RDE!iUlru7Q1H?F?@&MhNwbX#I8#D8T4F{2kD0Si~pYdqFqBynxl_C=$@; zey$w_?lifySO%fAPDrzb<7OEX0uwa>UoMg_E+s>i`nx_qu}NMRD#({6Cu|cpcOE%< z^Kcsv>Mm}*sf^G{)ludgO>AP$fva01#3|}J`O=dUibc)u_+-J;sQZCtSM8Op=p0^7uE^yfkDMp}>i|Wzs%8Bkh#nJGEF z6P)zMll>F(cfL(kbn$ zNUfaP&ZTqB@_fc4$cF@9ey26eNbkR=%@+?@`m_c;vtPie6<^kA4rL^eW(QDJw3YiB z&DF4Mo{aWrJKMQO=yUPZ*0!=cTTXp$*B#A-i3@aP1xwW;=CEg^tBd03NVE8@IEySX z1uakslWw=xJC%^KNin=|sm>L`IQ<|2m&?za>Q%QRlPf5`=k=*GTRhlUkgcxB`(>A< z*E%AjDnCc|XTi*)cUe_1Iin|T;!MF5`Z*oW_^OTpFug^Dik69a4{`I-al2AqUSI|Iv7@G1Or*;;z)`$@MFq4`U*a4&O&_n=9`^ z@%>q-*q3Akr5a1+JYCr6SG?F{#dRRMsqhc%)JRWEaJk3x`J>+mU; zFYYBu6ts-L-!hs8UZr-oT<5YRdEh8XYV?lem)(4m%AH2!l~4I-b@FHY7JG_TT-Vb% z8cTt$A$l65%OuNDsgp0s&rO~R;V8$?zAjkA&S*?7Vw*#l>h!ic;VH{I7m|HVnFJrj zd75GlL8xs|+?gzbw|+x~URso!>d0!?IP8uKLiuc1dl-UXH&G=^tf?x)z5ce-DdmKL z5c_SX^&$r9V!lZfe|gR)+rF>CTU_fvPMWX5vrS~U&GP+ZE_zZg=c5DAMy&hFER+J! zDx5_bT;pRzcbmfa>M0S4(q^Nz#!?Z9^tV3%4;ZNYrd@EH?4+OZ@KsP$W^+H|ZXJgK zLx6H{CA+US7a|Dkob;uP#cc>|J#%1nYRY1eG^SLC8wpq!He14a0>80BE+wDTTmHNH zf0O&D*kl}~!BirYpC!m9g$?$dL(|&7ZAyB=qVfBF+e9{j!kl{$OjGtlFWbf^bh|h? z7xR-+cy=_7jk91veY$PsxKp2~PK9nEl!*-c#Srp9FnCe!}%MhGBJ-8m&{TzhmtHOB4qGfoQj1hSCdk8^}gQS(wypYFxTXdT@|KF9hx-ai$&vC4Oty2ccCO9 ze;>-<@mqDvE2p54%Wf4e$yM!eOf>9V~c`V`8aLzy{5 zB>s}>f6j#8D=76opK=L0C48z0-MuFX-Mt3~gM@$x4~Gbk^!x5TOgJoZRm12X*lc3i z;}mKRewAP0F>u+%SB??LC^_Cv;IN8T{oK6g=--iZ^5NIsy(d5S?peQlgsGALrgo{U zV5qRj#}vfkyk3XXIvMltSO4b3|52F$;ZjN_WG3z}_io%yjWPz%`-a2+P|D-$*{TQ&h8aNBEvZ{(s#P}6eT+9MO=Vgmq2LsZH`l~5e?Dq( z4v*mO{)6!N47FiSwaK$>(BdHPUFQD7Ix$lo8ux?5nIc!xv(dwehT1CZ9R;zMV!{C> z5Z?CZk1&yk0M6i8AMoOXo?+dS_s)t1F4`l|G9F>_b~QCfjC0Bl@>QPCN?dLiGt3-c zken316th6`C_l(D>=bA@Z}GXuwVO8PzD2Wws7a}_S(Ur`wQ!kd)JLA%K3Es590?C( z?pJcga(wA61RQO$bCQ+F6&czx+FK8E#*E3cFdGNXUl*!jyiKZ^Uw__CMVF>aD5>Z! zi*#EBJ(IyO@J17kAXC))n%wkN2GiEu&$_aBo1R`RuM6n<07x;8KBly2PFdO^-)KHcT4M>r?c@xK8vef5@3_}-SQ@XkOrzu~_z z!JRkN&_7Vd+73+(ntMf&S~$$Vpq9vY-jod<=Mon|WEe32%?$V#@c35|sB%gF!KtAC zD%0(ICpbl6|J3(@b|N|Xjt-=OKsebkd&wF0x}4hgHAkmQAf|ZH=H(Jjzc2p01Kte! z6xhWkO^9c^9$!M*Q=r@2_F@-anjq-a(swxdxf0%MQ0_Io&SSzf6SGYZmdKeGc=l1M z&h-U)xXnI0G2Yxa6Mm-Z&wb81=7v$*bt9eRz05zhYtx+v$aDCoWJ}$ah=Jlu5qT#{~Nyr(<%JeCztfUrZ4iPV< zi?tW75JAfH`$z$gGmV3(nb}jDV|f2A0&nyLujs86}n=X^Tky- zR&vNAJ8Yi6D|x#^CBK!rT9aF?A-}k0`;_MbL}9q2 z7P5-BHCWlPN}T>odq*tv!diBzy+eISqn0$d0H&cHx&(>|TfhkRy7EsM_*~@cDt?^R zD(rs4en3x&sLiuC_`b6V)=|B(diylo2IUdvxq>vTlUu)r%9V%a1R*y{snO4LfHrWzmh7<%cJCtJxq%wXddkfR6KEhN)=ae37IAERA#Jx( zQ1(UcF%ue?VBL6g4IrN}4IpM`R&f!d#+N7o&-tZ5kip>Nhq~0m6Ow@S8_Z)gPMKq^ z)E^rkRD%ySo&f`QDLN3gRr#$xfQSODNuQrs{2CxD$jBJ&n6HpG`ZcOpFXMXvo{mbN zeUnzSWMU|Em0vA{+kouj@kS%LfE_Ft1w6TJuQB_1fde=?!h+*?Iv?%_(vx&wV>W8+ zPhU3xRUu`8L7LSX@94QQ0pkGiRh7Ov9nF3RQR#K)uW+Z$fVZQA-z|xxj?p|XedO9- zj?n}5JKta0%B)$dwwBvn|G?fjENY{J4zLjnZA#>N1chUOqC=@o9e8}ji1eka07u)r zmwly|OgE}4;`Cx~WbjeumkI9hRl>`|yI3E-EI{#G@bMuevS%jOQ+M(7JMG}2s+T$% zHFg?VjWnhmYT{~9(i(8iRwuWkOPF`x)!e)8YT2mps$h}}T-qE-iZ&3w_CoTqg~Mmh zjn+#+zxQ*!dsG|C>UPE@SB@A*yLYrp=rYOEr5~dWHAfA@{ZK!R;d32yJLkFWqOkK2 zC#d9^!znV)RkK9JG*?jByNvO*u>4%kvSLBXGwCo(hE-)Fc0dFKX3>sZ%}dAuW_h@K znJ4TEOh_WqEA}-HPwSA`$S|fv;Our91F}jk*gTR$WY4nghTasV-*X zIc^Qsg!Hs1uGSYFPjB%nP5d#ok!9HDkmu;mO`;WK_P9!6 zA1?yR%2#l6Y!-oDJlV%*nO~jK$iL`ur>Q$TAmjEAJi&grJ!H%r3Mhv}+EH#1%Wn z87+YA>4ct`(I0eN8(t*w%(ea6k+g;`#F9#pd*m#MHTk%EDwTcZY73d0W$rAlW{M&u z`pXQQqf0x%*x^A2hds6QE)f4tjHXm!b1wZDZP7d?`#x)rp+U+bl}95JZQIM6_J{5k z@;iK`g1r{}s~h)xzl)iw5u%1s76j2vwfa@=$g+88FFsvSSQPD)YlFXQw`?6UX8NGi z!b?!MBb~V_68tox)0oIT*5rE|edZMaoGnxrIc~dLU3u?+aAK3NEci^Rd0#puA>3R} z_CyTt)?vxGXF=xZj+%+#fz|);EK-1RrGur`XVP0t#>Ta|yX84X8^VZhC$ytbBd^Xj zV*_kR1U<~!EgLvi^mor~+VH!)di^1oX$7$5GYg#54{| zHO8r;x!+d=`AqmlhD2o^OV!xN-ieb6<<5oP@#t#qL&Wd1lCixFn@+QDn+f@c3^7^j zm|~x}y#Z?%6UgS1`7oEHgs}O2@w(u2N$Q|5w9%n~KdjcSRb!2o7roQd;KHz$_yBN* zohpad;*ctz<7=IqRvSaKv&Y=3aK2%kZE2zP5+Lj)CDDmzQ0}h{YQrdcwLqstl#(=; zLlzQR$LF)+6hw};N=zauZE}MkebhKP7djh<9SSHA(B{rjhOqHjfg9rGcfpwNH>e^x zZ-(Hpo7{!w(ga1`eY?Asu9EO9zl+_6S(_$pa4-C8%i^_%HIcTx2=**lEb6^$`e`A2 z4v2{?0(sQ!uRXaxrYQ68Js2A6!Go5YHn~-?;ss>Y;=lvatJ(`*yt>Bid6D2 z1J8XVZ-uo^jcmHZ^ex=lc{MVIcwGHzZ@Y{tx>>*Z5oTYiE)v~0=8mhjO-S|gp8pd- zANtAQ>86k7O7<=Y1TGiC{M=JJR{G;Y`YV4U(KBm3jVtS!E*qG;57g7|&(#^raR|TW zhS^(7C6Ka0YBwbN%h(Bq+4weM~#E6{Zmbh%IXQ$DU;$nmB@A>eaRf9 zHmZh$gQsWJ8$ItL^-G_lt4|~J08e(-1Sgxt1!KYN4dO!Cc7j8<@x22=9uTd0I~-TICmBD3hzD z>HR2Ii#Q_wr;U<%@s5{;n zP;|#_%*eoQXR=qm{ldG~|GXxt`Au&OH@j5j{TQE447N655?nuA1bX2tmv$?*o;%4W zl?T(rlWE<71%trb!uy2XMKqjS60Rox8=AUGrXIet%~D{_b8&0LVH&fTjJxpP3CN^X1dHI_uv=%?jK=pMsMS^R-h|z zE0u4p;o-Aw1K?HO@>-+mXBzKD2=N!XYUx$f_Rfh^>!7U50#RS1ug|S!&e`mFM6T*| zxkyAN3OOmUht&8*lD*wMrwo3%1;LOx;*qIZ`Sk(Ebw$N=q)AA<%x2F!tsMgJ#o-{) zEFkPqzhz3JJvo(h9h7-~u$a=uv^c8U;R1q(7F@3;j&1YfA?Xug-Ly~&WEuIJ%U4)> z8>9^%LXx6y5Xjp2&rycjj;pNLOthFO-;-s?ZY?0kKx`Q$mVhZZO|`FN(D@=_KkMmQxaQ!{}M zE-OoRG%PWhw<)MAg=5~9X>zliuunI0lAc)eI+Y$LoGDj6!0>YKm7LY!h{%G}xZuVx zOE|RbX};PI23hN3vhVPo26YgFAF9l|-<_tL0S5DSZRi;}N>4NQg8h|3FdR`skP_|%*?npY1(zu}eDdZinJx+#OZnQOE*tX*(^;VhT`9XL zWp^v&Ug-UUcSM(QuxHxK@cNeJ>g|F@DMMI)6=WJ5G88(4(j0vY#b=q9%rBf~IVZ^l z4#PwH;C7qWI_FQ7D`UaO(*j4rv<%8Eud!qaC_G_1wXDv@{|9?-9TiuX?Tca|5F|+O z-~@Mf2u^}K6z)*Cy9Esv+$BJ8Eka>If@|<9C@chb32u3nufKb{d%PpJ&p4;w81ME! zFlz6$*WP>Wxz_y6-<)gCR|Z>xrg^5hLRC)~pSqbIWUVC5a}^9D8c6mlK@twOxkh;8 zAny{jjys*nb=Q7U>_YhE#KAU4kr$Tmg_XU0epuiFnk_M=jMNCZ`$erwATWT|42ptMQBRq6ZR=NT0R$$>IB?Z?FyAvI z^P|rV&084k)%6;KX-%JQAhK5tR-zh^2enzpC&U9k>V9uQDuiIY)b#gHQ8r_YwLrhv zi6|$Ld5wrozuBWp%;-eF*+Isto0EYqBVAbqMVC|#b=I^j2NPVQ%Z=|Wn5kRV5+v34 z#AsSs4G><4B_Haw^bj6tno^U!&}Vi?8xWW4Luso-RX_MDs@V(+$+LLo&+XsT;m4Q} zX>xO}%-3&Lp}9~~^WFdKF$%Zeh3XXgDH9kP^5@Un zE5vuhs_d_+s9hf;ZRzGi$%_^T%b2(}bhi9_RPLKPM2->G>*&O%%@1uT6y}Es2d^YI zofMR9?M7|z+BD|>GE2}#_fn~Z9^v$zJ~9~E2zsyui#4}2gy`#agNzGmX> znd};aXfgC^vVPtej(g#Y)myf|tj{Uic359FE5SeD6Zp8-`U`x+fI|_*%L`rZy!){S z)>Hh!p7TYz{js|mvyrEq!sT83|kag&}2Cn^N`;_#R(Er0en zUn(k5S8_FV-t1QX6_e5qq$~@gY{ySn11^v{xdD~tp2Qzoq>Yt?;KjkVXCjui3rAh- z?lajEmKJmJrwF~3go|r8KUMBzTx&+%8a~IPp7D+Ik|yLN7P&?awdcV)#0T|#pS=Q` zkc_#9E3nq|G#mNW%%=G4MTEzxR+iMOP;8&89(1BM%z)2W$Y}Fk)T6Q2&>{cv&fK#l$YhGER|i(s z2mq{+VYg7dQVzMV>y@4(BLf)v(VG39d26Cc^(wV;y`)g>AkvElG6V@KbWdY3HSWp% ziy2;jdhDjTKQu&WI+XN#e5B}?9wcRBrqcR?^Lv{I>gHQcd4leDgU0E!wp#(7HA4^6 z1Rvy@@Nf@#-&Fxw=nXUNv-lU8YAU-&+i;KI$RTH2KX>YL%c|(qXSt@Tly(_P3fC#V zIi+<*{u**w%h(S~&cbs;F;+&-sg9l&>)S={I=v0hQUw?l(BOL<5z=o31jLZ2CH<$P z0|8IuI=Wv7oul6js+?Y-VY}Y*`#Jptl-I1<)bSp7qSB_NyW$`E5$bqj@IZbc@GcMc zf`iY505|N{Cztm#)t37Ko|3A(^YlxirGBc}DUDAxnI1B_A;KLlsfW|-SB!~2w5J{d zC!1RRi!Yx74QK8M>hmQ(L>~$j84ZF*TM;>IlZ={COR`&w)v+%`rvB6$Q zhPE04|Y57cGwiWV1LF9KF(4X8O92)`dDqHbC7tV+TgXkVYj}ZrU zyq9oPOSJns2$z!}JnWyvRn?Cz|KBV##dePOO*)IiQvQwte&9g$rhH*BQOI^odmS%p zvg2~Kut82MnK5chAxit;>gvy>vTHMV=^l%n;%1~X9PU5CoBF-ln6L4>t|${7zQK8S z)q{7UFMV|Y71Zd|>&QE~W*~O>6jxtNz>^~TUUmr(RQ+Geg$Q@dfzGVTgFgXfAHp*9 zLfSp*MNqfvC9{YN2Q~65@qrtlUg5$gpel}->RuMiF&N9wx31^8nEkB0^QZ{#i*DDJ z=SC|#^9ztWAxe1BNJM&^sL>13LrQpN6~IJ(hdREZn%Pe0WlL=J*)nuMv$Ojk@V(0; z9ro#iryu!86Wp|}x)_;WOAE)#yM@f(pmtNf{c}zEOy3BYmrsq`kYK= zJA_ws#P$hf?Woii27kvkQD56?U35UTA%nqQu0x(oj%UEpW7vNYtcIpXnbc+#n?(ei zT$x0IYh|R1b%06AhkE$RX3T7r5mgaZt#dkOsu`8^BSYvn6yF-c9XRVWEeq-9lntFaxgg7^h`aPgw$z1 zuf+*rK7ndECW_npYliz>NR96np|CTDJUa`&;xg~>1fUMm$ zku%8o@fYNTFCXp{W1Dh)X)Wjgl*`Czt<-!UD?&s+2zKuUx@%WW@j;VzHhv*&=?AlE zkJa>FtF{F%IMqx}sWz+witCDiK)fZ30u}ovd#ZE#dgm9@bX_O=(BPeBb!2Zf;WHIb z%GiD{CB}@yW{sI2zxoZO@zVqf-C~Qy?Q-RV3G&s_7WyOg#Ry>bTG(HpCk~V8kuqv= z>H1Dy+h|#KU_w0)%MLjDIaqsZU8W>!Me&|({}U?dQU$ZjEL2xwF4gc zL(uM)I%Qw)nbGOm;Z5-+a$MucZLvn-KEaA0y%>gZM)-n8YUO9Pccgi(ICFi^%!(_+( z`TdPpI%F0&S>~k#GuQsOhxTN~zU(t{Y)1Sw>BI{Nr{5HXThTTBbx>YwG$77C0{=D~0`N+}JgO=)$C zZR4oLp$c8{CUs~(A#l<}=dmj8dl$P7BZw>2kvk9gHXs-#-xh4|TA+WQbSUguV?@@k zkq4tt-z-w+5u6bE14irCzUOb#xyjjvR`QRX=35qc>?_^3u(O1!9P*1cWsSpC(NBnt z#O^=KvQ&G=PF4c!z6wt~ zCGt)eNAM`G68Oo3mQH}#++TQycD6j{89)%Ez=v7#Ok(@Rlamf%s4$0j zaYx@x#5D*~|CqiMQI1UKIeX9$KSCGP)ua^O7pXix z`f`RAv&btb?d3baSo>2A0R}Hw^XS9ZD-q>5j+CuB(+qJe!)rF#lrzqcbjV+Mza~ed zh&n;KkQJUpL5+#FbsH>Rs<+ZI3&< zF9qXy+kI?HJz9Jhzp<6Q!@tU0cLEMFDMBTu761WV8+-b7udNdv8_jDL>%xI^JoPrc zX^*1XCTj;5U11E(fXL%lUuOoMW^DIk;Cgg+C32zhN{9ZSmH^KSNY%V(R}Ux+8R>S- zcNec%E`?V~DQV_m&S+AE=a_KZ5q_E}M6+OJLSYS$(!%S+30;RrlKnzR({0fCpEA;gT!AZpiBoW!>L=_E-p_;Mtle`auYQoE)*3qZp?;xnleSI0ucuR?oVgTlJ{nHAkOZHR_gTQO-2{ zqh=H%x}*-=j)D^-X5w#8wI!9Vd-BH}og)ZC{hXcM;6yf=z<%NgoSMLCcFSJF#jcE~ zbU;1-O;XewG5~Ii&EB1@G0vOToRxMvKXkuxUapY*cD#G~-^9RGc7%-48k|cmhO^w$ z)>VuPL+0eBpYcQT=>r2}(Eh@F@PBXuY?Hgg$!G2#&bt_An_}wVfScsNDgL3hKgecS zHkFiEkrU4t?vc*JwOjrtmw}n={COAk1-6%dBc4Tkotk$l0!p_RlI8WrxJ?&dgwW&@ z=mT{zEps1rYthV`k0+Ze34>KX<2gtsOgXO4q-LJ6xQmH01R1R4U4^(plPT@Eyc!ef z94uok?h4(S>F7W6aXw9cN=&X=Yv1HY-*zVUvfzG{W3#d~t)&b7O7)Iyu8oxGQWWUn zd9Lhe4v2H#9oMOmBM-k>-pyxJHeF#Vd0TMp`MAE9%RF`Cf4jB@7wo5Go}0CdIMa8b zaGtGI)Q**(+7QVKEx%NUHshqer zgX&(d_SRLAt{(`MUB1r87Oi4D?EVstnff?tgMR;Is^K(|<-(6FSFo0lA#l5IA?u=X z+jpNmE$}M*Gb=VdE-nqBF>xOD|Kwa;G zODamu4^gp$x!qFv^AeXiaK!-xaxK?ei-xXgD#3Cxc_B9dfl@@nxw#$fYgIZ2{Hi3l zV`k1Ivcmy><_19#&eP?OJ1`8(ww|s0X77C9#2(|JHl)eT*|r;Q1r~pq_+L_t>WJa_ z{>rG%!}}>vr;vphy#u~$dnrDh1tQV<<+|qX15f*H^nVju0A~~iK!?CH9i@+!aFelx z=2hB@%d*I0x?b&R$K{b#jWsCy364i`1hW(x&IB{We5)}of8Uz=Gzj_xxSLv*RM}%!Nbsw_! z`I=Ns(tciR*H`(4fU>*_#r+4DD>A?aB7GeNoF%9)c!gSTyI$vkMx(n{9UqB3U^3La4lL&>lvkudZq@MJU)}HpF-^CkNFL?(Hr7jYX?j|EKNz ze`kOlN|56lt8oYg%?=V57`scFJq*p=cip(a&m2HdyxOGt}}EA3Yj3AY=7vUrLcDPbg&xDx_w_WQsQO)_au##4V{A%kR~9^P$i4jGGmglT zh4$x6^~@5m7h0beaT9Z$wayQ(y>#75t|6dIHAK3YwIiu(=LDO6;VMK(NATQf!HtMG zbNrFNf7@oinr8D4#(r9#%HUW^?8~NcPM?LrZ`wqIp!5t&M6l^9*{KQcsl&B>(=PJqm(BB-PcWU(KHnYY=L-0#q!Zm54QijVi~tzyxg^-Rhn7#hm88A>GdC%GFmC#D}D{( zFDn?6e{uV9p!9etQmdfvj}HInierf;vGHj?>Xai|{>1ZLDh9WAc!iav7Mo9SWFua$ z6c(Y?my662z{Q`7c`_t#1XFdSB2&#g==TixOGDe6uQIr1YTo6{zfWj?L=@>XRXNUs z3wvLc6p}amx@VQv;-3Gz{S){c!c;vkW+IofDkU%{Y5X2f8>hGgo~X5>pE=`A;9_ud zk6qSZob%SM_)02IoYE@Pe^hJyZ;G9EwDm;TADu3Wx0d9n4o|rM@Cg|IeaGA%S2|u7MsxNqa!CVpM}O1aed#(M zJvE`2dxnxzQ;|F+p5kM3AtIIUc^ zVfWHpWfL6euE?ME`;y>Cd-x_gB^(4KL?mQnv?tGypP(TlA|S(mA>g9mQR8!Q)9}4k zPfnehrsaW`!h3@Af>%nz)uRDK_3LcJPznj=CCA(+%zkMCDY3R+2=zu;$iA~0WelrkIq)2)#df{UW+$aIw&QtPij&Uj zeG1ko;fsrPXJzK>F+-Gz$|k~RI)efx+T=O4k{oSU(-+Zk3ay~{#alb{6k7;%;hwwg zSes6>O1w1$%T$=-&G+&RW4NQO^nA$CRF&avLfxeguni+e?~%aUEM>m4cTLGX7M**W zaM?LRDnsK}@I0(KcRd{PIH7yq+xrLhYS88`C_q+5q7%dln=c-<931R1zOgIk zOJ91Fc&*5q7qg=e^;zyz$J2!7VtpiQ75GFVyWsrr^k>EG87Z>@zY7lxLfBzunZn#y zf%2{sWTP|pRsGVz<7VqBsCX+dQ!^*X{(J2A06Y0Y?i@d=nm{1?R}SEjNGBg{W)UJ8 zi%IbQn?*(tc-{GNw^&adwCB|IFmsEt;k;2ms(UMJaF^0?Ol6Wgv;0EnVuHYqun(_9B+@4S~QV1A6-oHWl@$$ z?4$gDyCLWAhQ$AB_SBt=3Y(KoK*^0tg1(H1R3dpqi$>vk&Z=Zb0ZU&^Eh{zj!jdnYOt<)3u=I z+Q>ylp?DwUDb@EIeRywPV~fS2;I0?--|)G)Zv`~AK_8iBEi^|&s+%M;9$QrhCU$}O!^HXHAq%rCP1?QWx}Z0=09fyJ%J~>}0s{JzB7?)y7J=E+GzO2WM^PkKE7FK`Ea^ ztm+{vqgu+T@tVI7_TNOKnCjUJ%PQ_1eNXhd1s5B4Im+=nju0`;sCsV9?`PcJUsvQ{ zHT68PYKh|FaPDwq>#VQk9Az9wmQQ=ip`vl;7o296%$VaSY-l$2mW;iuP%B5Tr`lh+ z>a*n5#+jtkJHL=0oC`+tyiU}kX>8MuHCK04WanRE$(|?|KZL=rR@jY@O_17F65asx zgd06}x9;3*edy08!`(Y_2h=U-nNC&59T6jh-N?twlti? zCy`?Vn80T$P0@>1&m;}UFN5bQvdj>PQ%a;6&+R?MD3817gA1^3U&+S>xb*&rah8E? zL#qN!PtI0tSU(ypYop(?lrJ>Cjq=3~q+~QR3$#}w)J+iDk^>&8WfD}l_wb)@60 zeHEL5%dGYUp>#2sFHc*5&XZx1zf%s}_pO3Ca^QOvN4jd>^3D)*PUSYMSCJYTQ&{(z z)ulDY>wG3Ei-@+CHrGi5KTm}X)}C|s4E%r>Udlb77!H6rfp2^D4ZlD^i|5rjsN)W> z3r)FO;p2v`J1pg$nsbrC2}GUO;Bk1RrWq+k8mqX6VgMa;DYV_`g_Sl(T9&@#vVA=G zxgD>Oz+AeB+gqS4K#G@-%)wyVwiAY=#g)v?A&pPW&i)#~sY#Y9qE*InCXjJH<)alE zcsg5%h^6-QC#Mb?k-tFCr&hJ$oRq7Y&am=!d{bsyo4+?yeT->+sYUXk z+SxgmCT`68*>Hmcq@!Zd5~9LkCG*C_uGbzsl-_i}$6t1?JoMvJ(uZ)zfj9uqXEI1h z246P+$dKHghPf%3q-`&0`@!Iw`I)>FH^I&T9WJW~O>B0xF4nt_^OgyCFoqde2*NWs z%ts<34A?Z6@o^tLG88d_LdNY^9 z01@lflwcECs}yGD0gZ1SPTIHeO2gD?Z%ss;B@Ssdv3+UA8yyxKJ*OfGIz1CbR*w=Y zb6D+SnbNCU6Y7c^yKSC+?8+5J8%0o3~RXP7h0KuKECBQ zym9{YkSdde*9YKrv)ntWpdX_B=A5dfNK8YV-187fpKKjBnxcjbFkr6FLFyNyKj*D1Bd$~nh;lE)*GQF4 zG;+0nr6JoWl)eNOpgoz{j#ygIu2m+!r2Coyr7zv8z_cStu9=FYF|HbW7j z>RqbG+9%Fa4&)&ShpjbZG6sMFdKj}GsnR|$@%FxHLDGMhg-$XgYRLw<7w!b@VpbPRBf?>sQU=!L71&Z>upRy*I56)+<)D2-KV~)p)xljRU@adWa zm%w4{pFpVI%(W3-0rfi&?>%c3ErQqup zy$yvDHhjLky5w$?V3Tz{kw&bF#Oc`itrZTS4oV<-{UEI$|7u@wENLtS#4(ex(5NMH zFA2<+AyTZ6&8lyqdAm{+dHD^(Q0@t#@u5;&&%h2Or1UbQxf8C@2&vu8o6CPJ9SwvF z(5IKL<3|keN{)avuu^o?9@aqyb)pq!eR7WCO{JPPqnGr&5|1C?X|k2SMi6r3Lkv-M3?W3bv(XlosqHZ@2e8NBgK-6icU$LrzB%l(N0(DRABmGhU!_sSiBF`> zIa{exnBEx1bsH3d5X1GUMt2bLjG>zsdmhzX!;R);oVMW(o-10xm^O>#IZ+pMG*F3C_;7MlswFqvvN1IwY593h!#0|SL?;5=pS55(9^nnInzB?wI zT|KXUfwOiPXGNWaLl4BS=@j3~7K>5g7fg8uN>KTsa{RcfC-Y7rn@I|GWvtevs{Ak) ze`_ooA67c`_*G0|q{O4AXpq{yl-!MNO^W>&f~nzoFbW5{ycKh+WGky@wJ=>!+DsI? zrhoqXei-H@V0+XJs~_of)1jwtfaopxvgdyS)fmPK!(NMI1TtsV*2ZZoAJ52uI5#<+ z;cEAj&Cuwu6!8%-K6w6bXWTtiYwI(jr6*B$e36gwdi4}D&WMjwVGW28iOtvUv;AiT1``+%&+0D+x?UN7j|;D(+cbLT~(ad^_LO%v#S;F-OiV_@~h|DLAClAD`%9L6(i{(3gIkL92Sa1-~@yo#cJvVDtOJ z;2t~rUp+C+nuYXfNN-(>^mvWp>rYuEfM_j_N%fR=nl!|ZU zLy9%#$QE%XhT(aE%Y*D|Q9QZLXB7yB>B2dWsWc;@MTQCLNXu>B0b;Wx4c9)3ci40- z*G#n@JziF4y2556+&3(FoOxLRY2Pwc#ya0@D=}D`ymBwaZ{$dBok3s77&hDuKN#Kk z*9HF?rT^cHhL&6PsX1*@mb841Pt#TCRn+*I_ZIC#?-oM6WYE+S%hWLP!0Gn4-7LrK z#b~%yb7+&e%ajQM)~Zi^s&WFA#JW@yUTFm59+W=&iWHj;yjUzgtw@_-z`kMg-#@rk zdlH6xnXsl%MI-#hTAiu`6nm`&%9T-fxK8UeIscTyZLo*dg>B~QN9=KahJ&_XxbowC z(@Js=!(seLl1PlOJg-@Sov~-c+@&>P(x3-g+DD|N*6;QK+SR)#iJ$73|2`f-goa1& z0txEsDN6(rXTO9d0&?4L*`zF!=t#l{0898s6{g=}8-F-DcCOZ#^DItwN*{`k7*;1z zGh&)w87`JfT1%>|r(HbiR?vI{S@MtN#G5(3kLmny^8x|7dhKs34|3?UzEYQeqG$Vh zr`*Qn`*=6E-Dz2GBZyO}m)m(`wKiKOK8wir1xT1cZpq#;|9VEO?E?9BHb3HlBEEF5 zmw0}(nFyJ^xuXWoPvo#WdmBjGcATKK$c?U+=x=spn@QCAnGdn-NmG&85 zAg8qygfO-85^6E3G||73T=>`kNdhguq{ezQ^Rrdg&yurnwP)_>vy~-TaEmQh^K%Y< zHowuh!BMeOL88g2!lJ>iF|>x}{1XE*Vd-u)e$Z^RCx^?0-v3&Q(4r8zG>4l)VJsMH z_7Frxe>aP*^TW(z#0f~%+X^&!!?>RoC5vNj=6FSMyvKBMa1e*jIhJ!HuwTLOd_?Y< z#&XcAr!)dTw;agE#jP$hL4LULINhp-SxTPb@J+Mxs^S=rTQR>o zfh<;soQP|ot(6_xY1BK{Mr^V&#hN?L|13{$thq_Wf7b(}}1IpgVnh`@+G;m#x}_G`WraqvKb~ zO1so)`gM72K-5JVt6p+!N3=1OeSdr6^|-;8l8Zdn!a1{(3K8!ax;x3vc zLLmID5fkO$+{Y<9%}sW0Jf)lX)zJRqNA(xn@}jWR4PJMxxfzAhb4x{>Bt z`Ei9D)S*r1NI$AOI=zS1ynIrPAy4uYQ+#Ggfce1~hK;8k$7J|^NH};H=wGQwfbwSC z^*BJbj^9Xlpg3pI^^ z_oh45J$g~UhV60p2Whw6B9x1#U{8o3fmr!R()V{ee<64(gCrEJV!5MYjecB`?%NMm zJL3s@Idpk2E{VaJ5$SU;yM|X0MO=2l`>(m4eHf`<%%G4>ItId)iGCprbZUg7S7MHt-C;$rld7Cpet;Uf7SL6}^W72XEJ2M$N!eOwl^%S=;Zzp7lw{toyy z(zYZXWFpN1sQ^Xu)J@RdMlm(t-1)Va*Lb71* zCg2EZHR6piH8SCancL>2L8rnOSGVm(T)Z%c-qN`c&R&MnjvlVIv*-?U9%-t4QCJJL zY?KL{rSd5V-O!dG4Ydw)IL7BUER)Xe`3|)2`<%g=Ec`OVA)VBTvc?RsbH+L&Umzr! zw^g!;3M4>g0^edr-w4Up)SAdtOTa5nAQ2+%=Kwv~%H&^DMkoG0f}%b+F$hg3%_)3&)u+qA_1q~maE@6% zho&jg%tDWUue=nfClX~2_Y;xh@m0#rSb&wUAv zl7fp&QdNLKB28CX(`^djA{8p?TzYm>w--8u>X={klT4(@>#qE;WU(n|eRcM-SIY{I zmLKu|Ud8eo@Uh0e6iHS250w&-B^S4c!(YCf@& zEZWR|@Y=Zt;^wbJoKf0c{wJU<6Z8**HWGptT!tllM`9~5gxuolb1nP%7Re-@$i|CD z0#SdxzR9gGeoLFHQ}(@x4l^f-ffaHP(YUO>Y#6jz`wBuspG}wi5~>;KcvSLi=jaX7 zYyVouxo5mnFspI0h3eIj3p#y>QshF4>n{X3GPYIJg_p~}ERis{~fpn|Vy*0b$ zT)0I~shcCNwA>Ep&?+l6ffe=OJFNx>6ClIYZ`!%3YUh~Lcz+ZLhG_3go%rE9Qu50O zBk{dH!#YzZRB zU0Y=?W;QU=O6j}_oc)XT|Ai2DQjnQ*+5a;j?*?9F-sPrPFU5}9=?(0Pcjx4JiF{NC zSiF7<%Dh6lJw>c z4t0vDq^@mRB0-z1oDKo-AWnvZ@J|9Zh7xVvj+8w0?T38Hj?eqmGBr^!vk#{GOe#flfygqcdiUx$J=V-DaqR( z6k334k;FyxD4)mbnSLQ~HFeA-JdRlO-b%H8B$RgSZ;sW%8-7kJ>PxeLFsNeOW#3^8 zeUa&(s-Q)-eb?a&+_arWk{c^eTa5Cdpi8eo2w!Q8^F#Z%tGDtV8c`PICVbZ-Hb4!;8gS~?eF z$F5K3A;iAG%DW_C5Gmjgee|MP+aZ$Fklkn5%Q?t6a<+PE*kw9KVi#m+Qf2TChuW0A z72K==^YFmE*5UFZqf;|ryfN(5m5tif8_q`ml)w~j?+Gv@zxB{k18)m?z;olhbak}m z=GtVM#d>mnxmE2T^fGgq>{W~R5(L$pB!fxn7XsWJ{AM6NrjI-!x_yEThzIpTJ7_b^ zJYW}i4y{bJny7njWL|)a2{sf~sN9J6MGo%l)J`D-A7bk{4b_cRvG_pP;O7gHm- z1Zlv@kd{nhy3#^3`%y|~r)JM!zD$^zS!Zur-LBqKVqX|r1kfT5nG1i56dZU@9h}@?zwHA=Q`Me;6@VSv(!D*uv>q39WE7|P8tE@ zFC(n8hV=Th3VfY+pDY1qQ&5hTjqEQCoxm2a83YxS$YW2gkEKY2V08Dv@LDSq-6_9UxPb#Kf4UpD4wxOy+w9y z6gxdLswWsB(oW^L^(r=u3!om7nY521Kq6B*Jyo_zJ&ZeH#vhmt7U!=Nm!NVJmy8Kg3eM>Z>ufT6Y<>Ns#v0 zG=B_;f(51`nua_Sw`edACDf6mDsVUyJrb~%;llG1zjA>-Fj=#vw}o-^W*Qntl8cFk zBk}7Bv~&pRs|klK^7wyvG9g>Z;5d!(o)eUr%Wm%?!ip$k;6_u+JiR zf8^+Rba_5KmdskrDOKek7C7rNPDA@H{gMuUuJHfFn6GJ-dNRwrsME1)$^~k8(rof3 z!@72t;x3jl!dP;cc+{)uFmR5al!m-`y!LSR^$O#+(NrU;cQgLiI-{=&r~_3c|6*!u z*)ZA8GyOKq7Y-5}T07GBI~028sRhfKV^a!|Kn?(Y!g$IdCzJ%s6ou%gHLhOQ~V^OYDC8n;e@?-U+7;$;T#+BJspHkN5iI&x)O@ z8)BQ+HE{9$#K;yD?&wEuIly25Avb_ieVS?Zqh!XIYQ1s&rgl~-zq;cugb0-R4}I3q z$}(%Z}aUpW~szwmSxwQM+Sj9ONFT(ZN`E*;n0y+aOnGKOSM z^~N66P>TcXP+80e&tc)H5E~tHX@1nrpS@VwAf+T@N5{ba_B4p{yXfEHbt`TrkEhzG z&p0k5pc+k;dxX+8ZZ;3TtEpkhsi@4(`j)@socnX zOgzzLGza+Y)AgGs5xY1*aMOO(jwui&H(s`h5K^q5{*JafWaOrswjA(Ak*?P*nJtrx zA3)jBGBxz9OcEZO&LJOMtXL3CSlp9_DU@PuA<}v2^ucaJV3TZLVgG3<7CxRyvKxj| z6J0C`TKNc0+V0Z|c_Utb%x}_hYayLxPPOm3qTdYlaE=$KNP_p(`wjr<#KPPAg#0s$ z@>JA*hywo;*6-YlJw&d0AZeNoclqfCLfXdJI}gUa3YmH^h&LwvtzM4vj8RV#VZ>Qv)bw6Y!Sq>%9bjDK%?k!L6UNZ8$e5dJ1?W9%1Z3Q=Q zJNTCM9OU{QXkKP-Z+vKTYQ88x>%OClUq=RxY#MJ}UQSo_O$*x$EgbfNp>O_u^1&h{ zBU&+sGx}TctQXvhxzZaD#En+1e<6G;R01y`aRlD3 ze2Q7Ob2pLdrJ4z>=~{hDp%ib0DOf0LvnyAj>N&(yyP9k2rd38MNqcuG;-Rf%E0}uuVVkz=V zW{sb{kJsS_VQd8~85snJ)c?J5_`enJ#ennMqz@S7#5f4d1iXb=DUt^pyJWa7n&c0;aVE zHj!1+`Ej4gxa?<1N_Z{vG5LgJ0_Gw{GZyS`Xiob_^T%Y~pf_UYXb4vjg}1!l*rGn1 zCYw~OS?Z+NR1qpo@-+NhcQ`1@;$?0Y16f_>dt7lJd~2mR zCltROUrCe7h8<4J@t4IV>9U(z_FuE!BFF^DpOhM=laUw6jXe`c>lMB`k`b@mCmg?IL*Qf8mpq91cC+1qGUnSEsrg`(HeR zZU-yMj+GyV+}=dEv|zJ=ZBaWXs@fL+%t6L=UHY+Q%CaEh!Fl%Ot$ z?G{!o9bB?zG-xX3i$B@M-+&sF-ie!C`2~LpD1PDk#*4W%%h_QmClS)A+J||c#?rJW zl#o#|tno32d-sdytnUx=c134y_^#F=ql=fZnT;sa2M$oubAv2nA-av-Bt__=qoZDW z@@BHUbn;|wybdqr&6X-CFlDlDrc=Ic0>`!58Vo*(DWwcq?? zj#M~mC={&;%n;)pO_$Ev(q?tcU=HIgFMH3A$1j!Q*4A|CT-{E>Mwjt~omawEhnANc zeSN$t?1i4^#Uo#n)+noD6y%nyOt&qA^=*?QlAJ_$rs|mTo%v0t%6(sOOF%av+H;sl z1f#wMqoyq#1k!0-bzHH|JwPAJw+>Yk_BH_TGb=Pd`|9k;a7r;^T3!Cau+gM;nKJ%= z`#%082na0Yb>l|ph`{Q>&(bxc-?J6qwE!Pe8wuYUy(U^4g9n9T=jThox98+68Ru4) zlfQP%>KH=WE}Fv!?>^}#)>l#?;(}r58Jy3*qbZ7HNsa6nZ2x~dWg19oc1)}T%O0NT zXDjp=Njq;?6A{gvm(T3tZl<}@E>wFqYw!6C(9kEdE+qx&7+p>`@=on$2tt!G4NX+1 ziKwCkkXoeXQ?!&zeH)Ys4;w-q= zr1__`bXf;zYb{N3DUG918e)V%^>oJSxR01_I6ct+hrPFqs;lYNL~)1U?hxEHxVvjM z?hs_-P6+Pq9^BnF1OfyJ4#8c52Djjr-g)zW=O*X$=yQAA9)0`!PXA$zMXi!mtM;r} zb3XH#D_pp%fayl)eVThx5(fp0@ECMH)Q_xfg=r?_Qd;U+21+3}?pI?Sb7 z)kZR&Q#XbH+zDijA(Xda5ZLkG4)$-fDS=sG$z9ZPg9IU9#h?I+gV;#Ga`UXTZwS)i z&ZZWa`&vdyfpk<5_XG%|B)~x4~s1R_|$?DShE* zvj}GZx2b%*KAxXN`|=BUg5z913cI8tqIHI`#Dn+uVR$N@MKZ@BJ(P81kx?XvOt)7Z!2N|LAKfw0RE7jdT0Fo;~Tt3bZ^uveF%HSZ$_viOV(qg-^C8?jR>MC+cQF!G)D{2 zWJ^#-)Bu_I1V!cw6I^hOgN0%;w+y2+TclGG1xGMXUBCvg)3!u<@;3`3LN#s*l$kvS z8ImO;3e_L<4REK=t28ylZ?W0HRx&VBxowX1fh%Ezz+PZS74{a4-6llarx^?nw{Op9 ze%$BirEleDUe=?ZTUt)J*Q~c5G;JfAQBRLZ5;RNld1c~vWz)BjN{_zL>r9+zKELqW zPS#+u7=$mI^lZ^hGc&@4AC!q>0Sh=Z=_;9x9M6UmtqN|AjC#gyO(5!~l!23TD7@fq zHgk{kAfVDCHSFxZ*Nh`pS7hbbbvQZ7G}=-d9+Y%8Vsu;fyS4qpo|g{Q4f!DBSd7eM zfdqsBTM9>JeXj&90ezE}AH5IYl@GO7E#z=F0L&*v&s2+wI4S?|PFG3$c;gNFmM)6^ z5~P$>;Roj#l7ZL5tQ+dXP8d>+o3By*rczCZY|AJg`9ePLnM_X)ic(p)_(V;oq(GN3 zH}Zaf(7)6za9<^FEkve|=-Cwzvqd({LO1{An{K`Fc?0Df+c>FKX}SR=IZpUx{#Kic zy94>?OVbaCd<9O~0G9lg3lqYy zg$u0Hssoqlv9m7pkYGH_h<75>A&rFH5SUILNF z%XNl@=}q;SQc0poI3S;t?JUS*Xla(rYnTA9%S{(L=TuI$6|84%Ur(-abBJClX8YG?Xgsg6>ankZJ7za8_nlWUQ=tP&SgKIYgNrEYG9uUr&I zq*6klXJ3GP$q-dV~#fMfZfT|F6ySPn+kz(}7Vi>6R^0-*%X#)T*dlRI!ZH z)guaJSyrY~?lzmD)W1xN$CVBFp4(?sH9ur`t0l>q!ld3kK-P(KhS%pXs6YH0N;whLG0 zHgO5L5iU*xIE8_C^SYtjVWx{L_dH-`(JR%IO&9lguDpGcWDHgQ%E2#<^^jFJgfVkf z(qtwac1E=HSNx|{o}mQJ2$Lr*R7Y8U{5``I>5LqVdlJphr(#u1*2p_ahSJ8vthQH? zG=S|f542?vk6zT&5}zCq$|CBBm0lK-XnBHx3xtF36ZuJXAtU!lXgmoe*w&X`P?rEh z%MQFt-7S7mE;(9W%cwS6Z90f)$YX+;gOX_@^6W7`bTXmRV2zaB!b_a99>ZkyBF8*r zk&3U@hvJZ%fpQn*2+ye zU!4W)k7H^OYaGqZ>!EtApOF#vau>gS5Hf@L)`h-mJChple&QGtXO!TB_6&*bjv$xV zB1ML*!JTvqcX0QIVJDVq`h#D%>rWdfCKODQJa%gh>oISOF-=CNBwiu?fOx~osH+oc zk+@&hk^b`(kxCc;G3JAm0%bluPUHKESZ#5t#5=v3ZlhMY6c>_bCm=h*ze*-)vD24yDY_VZ$(D=~ZslO}Uz`uL?xN*nH# z)$N72>kzZdTZX9+lZk9gx1>pO5@}2&bnM=N^`}p&TCnL^5cEM5+tCAOt9q)zi;FtI zvO4(txz$juwh;J|o3oNYE-z}RYfyUy7%mhSnQ(2+xK}j2F>IN_yTe;L1f3l^b|kzM zP}Mi$gZwU~aglg#?4+QINjlgM4hM|Pip@7j-7=WPU4YRuz|o{@F*OCG0pXU$-J9TVY2{pm){*rVkx=4&lY6nBhHCq7LH(egFP3*8a#|$sodP z&hJy}zw+sUniaYMB~7W6TJL0kr~~NkLTNFKLj$!QM(bue6p(5sfoVX!cd)8z5jjOO z{*5P$nVwC)8zTiVD-}`TG&NRKiyxXOGa{3AsZ7h1OdJAqsLcXQVBchQwM3C_*(aMS z{s>!?B*Mzpk*;T`BGXp4Hwp3#)$hKf+`jU$0cJC00kl~((^k_A)-PvvU!$jam;iO` znQ#2cir&4ZUQe`p$Nlo`8{_9j+gB(nm1}o?#ay{E0!oF0?~}yRA!{;E3PRz~9t4k@ z9txI_wO35Gq0Jgbz#Lko%%`c*FA?X@1_`=qfoB~Za#FrHXv-C%A;60=|l8LD+@zp=z#Jm20NSwRT zOk_Yv$=raKlCC7sf&DIQ&r(e%GP%6>1L6Xdd2-k?Mih=Vb&j{Q=WGwkR3Fq}0ySps zt&wev{$b+&uL?;4CcU8X@J%|Wtd+Sxs1ooc6V?u1wq&}u!G5Zo7Lato=d=qYagP{j zf!hM)*DIN)X^Coi@mq>m)}8VY9?>e$2eY^xka-t^gF(#A6RAS=hgsX1+x)731EbUd zQ@+ZPV5}(w)xH+PAy$KzU{WUJATmI03GhzT3HqjOVGM03fXW4ZRfcb}xm7WM>XKP}c1`F@O+B;ul0V+Um9Z5MFSWf=9c#+(hcpmwl13gwdgv@X)bhP7 zgFL>Hp=C;Vi27%b93P$KhNdZ#YC%g&oiWsJ^SQoqfDXxp?;|xZIINw-u%1M(7?+JqP6u9XPhQ=N%N%i3;vMj$y=?4LHlc%ER5liwwYK; zmxG3m%Ph7Tq%24jbt#Tr#3p;n7a<2?FDg(iXp6KVi$b-+8-qHR1E$YQ-_%{mYat_~ z)Vd6Q&%9GWMYF~v&PO7PMBfV4zU$ssrDMFMu|zImko-HT~N zW9PW&o}HSy`?-+>RlF3<$TrM-O^R>$os=CGwBa|&$G1ubcxGn7x=XHgc zn8_M3WnOT?sreXHX&y7#Hx9gwXcx|m0~4s6DgSW>V*3R@md6DQV=p0cH`k_BJREqA z)<+bKd0x$+<;+#L4JR`9)ak9tJ<2qsGcv&9G0h*g$ZP?kuP<5SA5Ze5V0CVERt}Y85!VJ^(-BnAwKLi zOCGn0IZ5McV1D;iRv)18z*tsG&UKSXluRH&2KR5$Rk*0`Z5|VkOLy1u!i}jod7YKq@G;*> z2IZna==hx)%+(tP*~Y32)JB>uwfdNOgopk2k->~U2+y(#QiC03XeGq%GBg~w8UPHzw|#87E} z7MHn>OKz{*fi-?gA}zvX${|=7=BIobqH8;RZYw*YvPM`>ExMGMJojM~pg=6?iai*? zOaF`_5pQE)I^Jl%sv__=%AYl!j*NIjk0RWF&w(Wnh6MddiIv zup=Fm1@dBarpkzyEyw)6d&gH6aN4r~l_)uoEgN!^Ovh~_r8o&rC=6Hol) zqx4av`%lB^KdBusV@^t=pi13hm>>2S5CL*%O709)mM#E0Bjr$R8Fn1+qFaV#R;T5) z5l~Ecyr_4qeV&`6DA{7deG>U$PaUQ=)GDMUD8xB=%+)fiqTZpIgN0&Q?v74(u;=8>?^b|W2%#+9<*`ilO}I7GpM77 zh%1wK>`FaFrb;gPz{pc{sd={R+7RRKJ=6f+a>2KibJ_vdJ|uBfcw1vPwnXo#>?Eup zL=fi}YwiO%g5}UhvUq~^bLOt6y#;T`PeBI`M=K2>oN$EE8QvAd6l$0a5#Rilax!jpLuHqEH)(+wL8@RyFM6rU*<&^Ec-` zJ3`x*>JN$fVo9=CSCM#M`0cAck!0G*nY@YCHM*Zr6ffea;@KHUF!x!yZUMU;&uf$O zp$K?jVBI4C=Xyn&7AD%AgMH%X)@5B_gFsorSu9UN{-Wdu#BJnd<+eWg1>X{UIMRx5 z+->*Fo%xycRCXC^4X4yAxkw4rMum%f7@OQXWFDZas9u;U-lPZZL5@9MOb`2KHx<03D`A<(h|nJNdQQ= zR$01U%a#kJ#ULef7Uu(IspHPA9_@9Qx-cl_@8}SbZ(q1R^7~$q4nsB(F?ELP#B>fl z7*5zg#oGr7j$FEpeSC^?SXEC87yaA;(Es3@T18~xi76xLk@)p+c0x)9TWn$kD>OQE zUIO^l@J}d@4II9Q=b$v_dg44G81|$0>a0PAAStP_b$dNrJ)+QwldG~Eq+f=1Acsq- zu+2z6He1{1tLzRGkj_Y@URw$P?5Fc24kD;3kN@;0)h0RCRnligab>6Sg&&kGex{iK zyYd7`nXXs6+};+4Mhg^ilp}Zc>ScDzkrL6-k0cw;k6Nc}B`PatVCVRP=4LOgsdf-c zAyZm(AaDMMcG)7&%|sKgG)_Mc^C_^Aw>yx9XXJj`A`oy+gt>|OEgJceafe$fUN#Y7 zws%>I+yWUi6+*@1Y`F@O8mi`?K) zM0RrTVOX4?njE?KMTkH|Idrgq z0Tggph4XI{XYtp5?=Xo~%EIOglBNPaF^Ked{(ksUA2l<U=Q{5{OIih+8jmXSuLOKNX+|+MrOe=jaZGDVWSyd)B{9>)v+}uGE~p36 zs9k!&SUndW6)PT+n%RA6)pRPFluZ=3!b08|7Md@whsT4VBm*>9wjJ76$Tfa07aj32 zA8Xs*R|wK?&B%~o8PHoXEH-~n!NAD#xi&qZt6(-POlNkW03lCub;>QLBwg71U%IC>X= zXUe>YdbR-mHF`dGUm$S4+;L}0f?!xzXcoH^?T)%m35+iCB|=xmG!2>NSm*9~EX|}N zm7VqhH*)E*IBXZ~ZM&yYsgIn5K33Y0`KIoaTEVA|MDNg=NdXr>WXD2_dG?sQvA2JU z=evCXplmCqR8hdf8rxdcH20O)SYec<_q~b~+gpKht#QARc|gXRVv@-Mbv(|D(Xy)P zO9d&mwnJ$qwsu%1ds{q#s;D{&SG}lpz4wu5w|wGzto9u*_poAbRsSOD}CI zy*(fx`_}V4pUWb!*9rKFg0nST#dcyd39KxWj@1U|jA?ztY{cLV(WOkj z=gH)7TGZP~d>Ah;aUOCoEwKTxlbP7ojDX?*-umMb`ZH^6JV)-zwU)>=WcjWxSME7? zlYD0`QUgLXSLmEAI8wzkG&zH&#g4h7vQ4rT*qyPJ_WDhL+Dmij1ISW>N*TLy+(6eB zEv7~po?))RDC|2u-z%QEdO%!Z=aT740&n4ro)#De?p#CS4v@$$Bm>N^t18}ljeoXl z@THnM?W_i_;zXvKq|328V^}mvuz@Opwoq)h(!m=5k?OYkeQG_geAmdri{pJ_R&tWvv;U&w8t2r|X10j4E+a{R?UJCXVfPQL% zo_>yjr=75+?U?n5DtS4?O!{eh$cCJobucTH6?>zA91&G4II!q-Pp6^|+X#d4she3* z8gEr~5HPi`7gz=##k*RYeBEl6gfF3EK8vtASqp}I zL^&H5z}DbrjSDl@16O!-SZ$?G4)^uoX65zb4+tfh6v9_i*V5!mU|c0mcrP&{f#o`f zLh^E@`+*F;ih^f!sdb?_IHILErfxpYa7L zlj06wY^M1Sh$mqA!NcsVdLP>&O}g~UB%$u1w%&sJM1|0?u+P%LPZ=9cTBGfDCm*jZ@Dbl1K zWR85m2lXCy_9N9E$kpK=5NsU?CXtlJ3W_68x!;NORsmHLzNFmN3Sr@H&aZjJssv=a zB%<*r(!x=eY(AZeJWV9R%2B?#9!=;cV_0{@ePmESN5F5XL{4LEm<=5YO_X{Y2At?S zHQGO^QYJ7>KqKa*7@|?~4ScBl9U`L*x!FUizL?`MY&W3NU#}7#s>eb?t>rBJF`#Q5 z(6Y!if;HH-%V+n~Wt$(fOeqk;JgWg6kU>B-+Ul&do?>5N-pI$w5jxhJ0$By4Mx9;n zu7A%r>dmaq6hEkE+CV9nWq|v<*5)2dfcrAhf}d1Yt8ML}5+Vp~^Q|h3KyzB;EK)X~ zZw<+^`TXXN{r(=#Z~g{=qgGF24^PXK;)?boBPzALs%*)D(hWOKv?H-nh9-YU6YsS) zGI}|aw)>8)eT>xaURZ5TE8?R5F{^m=Jji)4mNagZt)1px?D@3R?LY?yQMNk!N)toC zLdLOZIzp`bn0%)yYkG>V9KGMP{;Lq#M)V){FJojF_+yfuWTyIL`X)?UTO(Gjo0;s$*60J+GU6YR(a zs>*%~Oyp&{t`G(KJ}KNU5&B@Psk~0qUpQfKLZ!0+BT%i zq)o)KYv5)yQwc3{`Y~)3v$xmKU?k8Kt!w0}Y*U$+PfD^{(8NN2@UfC?$J+@M9zR|I zd*|h`EFWl4WUeS5#fx?f)}7wCCtPUh57ig|?c?5*Z~l$B(Q^=}R^xJ!zS{=edr?1> ziM1#Wlro+kY;?%sx+PJ7%vY?b(b;7{8uQ)+lx8IkZpYp(bv_gnO)Jv(y*t{L>5Eb= z&)8U|5*S&A6>s>E_LM(pMdtPw@*!6S*F&EoFM)h$zSylDWZGqD(+qCJgp2HH+L_h8 zBbsS-jj%{Y)u8jv*%3GJys+}|LHZsj*24TKfveV zFd+T_EO}UZA|3Y77YO#0WGYc|Z4T$T{@qbt6kwJ{=lG4QrRC0bDj^!PE|!WAi|fyu$II<+9@EvR`?WJ>cx<*9zc3_)M^9K|<$SmE;7k33vrwb&RkQIi^taxT z<_}5It4G(WvHBHJ7|J@EJG&1hC9d(qRrBMkTrtEja(j)qK0)#CaOxIBCL7E_(zAyZg04>a0J*J{^x1+LoteKP4Oy+Lt@&Aqp|HT3}v+QHqM8C{yTL!p1B} z%Le?67D$N(lxRCm+qI(2czlDDbJEaTjt=V53(l2I)>IfB4eNy-b=tUsu?5tFE1k>^ zvnBVRIX_b}Zp;a0DapZlJ3lbuHH9x(qH%OIEM4!-`;C(A9bfw0=_z@*Efu2@Sn&)l zw!XEY_a8Mgp3A@Vbgn3o8ei<3v#&5CG?(}w*U{DcheH2$vP!NgzU3v*C)3pveu}Dh zZJ7+Bg!^UZCnKOn%K zK?L-nI&#J)`GLq)%0fn@=4)w6yeK89doaDEWL&UQQw-Yh%Rv=#!zD07mUizk+b6cG z*L>CUlfWyKNjW0(;g~cpn(bT72+AkYp}{zeTDpghmhw$Q4MghK>Ey|KRHmJ)Jp>P#oUyA7jJcY zld#T3`NbbX5Q3;!u0mlfjVE4vEDQo-Os>D+P$JeYuDSmQS%!zjsqeG2jn=J$HW6jPW;-}I+xVA>}f1h+;~P(AXfqOnTR}I z3&iuo9`BkP{AWGq}mdggWa`` z98u3gS`jYD{hdkV^Bfj<^;OWHg9VEl`PY*dV{>CvngXp%kYwYtcI}BCS>9m}Svnh9 zpk{?JKz~PJ8S0GpYu{h>PL&c@XS5R1f>B;nTcPLLRCV-`<+G)^yo_X!WKXa1d?9ly z@`thK|2!~L(d7^kIg{FVd(wly;iAx{rihf^6|5!U~>NE z7Tfcf=;44v_)YqC^dL1}bBNr2B9ADbaCh%UQ`?nBJcy;ua#c;*DsBo*uF^)fjt2l*1z(O4};Vf0n+VyznjdWAvtKrMkY;O2vnvzR&o9C?aAl4u_07pw6=*EM)Zj;I!b!`58We&>GZuTYFQdspe))Qq` zp=Q4zhESKttt?#yG*BTdgngjOGrWEh#({f-pCX);)-O!>dJ2tTEOoG5ZBPhzduCIp zw5X#|=u;zk~mUa?l?fu%*A|aI{ib`ppd%shc4^ndv#oh)XFeQZm$QAPz9AtleJ4Y@4K4fc= zwlNt$H|Cj}E){o9I)P)z77KW9+p3K=A^cXbT`z+Rxc0>YVQ09k9((6Ki=jiCjg=fq z#n{{9X;BcK02aa9FGF^L%hjr2MiCh-Nogl%)Ti7lje?D_ai^?Tzr4$15iZc%eQ~nw zN)A4IKy^iF{oU(>KODq=(mrIqC=3aFZAAxOd~+=u4(Y!+a-07jv#kbXdVhDd#NOGC zmSR*+qi@x!W2yaD z%F8d8PG&Vl%pGe>pDly-&MW2h1tlsIKeVBmG;TO>wxw4?tO5s=9G>Xd{hQXMN2wP4 zcN&+2+)oJy`ZcVfBA99VEXM3p_inVw_9kt3bXYN$w5bfd-HMG0FA74x>!I;^Uov;h z{}w9i_wQqq|Bnk~JWx?0w)3x-8S{DJ_V*bjpq?Ej!KEm>k_0&G?P$pDIG)eljocRN(^AF&A)zTlp_kRc4{+AO; zp`^GG`4o4`1PX3TvsiLiG_n`o@Po=-A*RKPJX3GyiIk-~OqZs_&jiSaT$h}sI#05* zrvJ;q+c!YY0~qWqZ)R3wvyNw703Na~K;21wBle;gwy*qB`?-6Y0|v5sJ{`@GWAKw( z+R3e+VXt4hxDs|}OJng_tEDmVY+o5Jt<@PVUT5i9hJ1y{8v}}#Z@eL-EFMvu2!y9~ zqV)h1qJ+(xKrDKz2aY)n*RKJq5y>?ZY2xgLh)pi4Zy+6den)vKo)a_aY)Wf~v)Eo% zo(j2U$(mb(m}z>Q%-*k;G&2<r_#AW&P54T&o{v{HJV&x0*p+ja{u&#Jy3wIovWX%CT zIySUsV_auPHR_BO$of&6VF6e*d^n8P7R4<|G?8#=Va+-*R83jXz)oCjy!vi}4h#*A zSPHcdKgxX9Ongd4$6+`>+#MitFdGStoQT~Hv*R3UYheAAT|kDE(h2n7Pgl_+WTmRS z?0Ru#_y^YFD@y$~iJ~edm@4P%-|(j)CM%}!?&K@ND^KNbc&QzRSRX#`)V>Zraq_aA z-4Mt&)ct2D2>E@uhJU>W-e2$WN5QWb{8jKz_JaQ^!~Oev`24l5etoF_&Vu_{(8~6U z=9Kk!n$w&AMf|7#3#nB9XPT02Fh@$GFpE*O?F#mRAHznpyJ!`A*Z9xJWvZWERu8r{ zBoir5V1EzK#h@`o&<2g_`ATJwK;wrxuXwct0NyDuwk39V5i;#neeB2wRhDF4sxm|h z84mfcJJob>Qv<3JhH-}*Y|E>($yma_JQej9d)RkLp@>Uh3q%EUElwVTV_l+99q#g* zkAnXBrbxEnP6p{h09LE1?fqwkeRb>z&c-1hNJoCieae3d3R^XcfsI|NBl5&=n*}AD zaNu0TF)eGQ2@=f#t1d}NPLua@hWc;wIIXV1TsoroFU3u)*n-^kNQAjrUgAvADOicG zjjMbwOVvgsuvZh(${vC*iQ}@qwpXgeNt%)gv*lc?tB&kBpdrplhXuz#d{!+R0U_^m zzL;`SftQuNM%~~$DyGwl1;!b&H!XBJwc^x zdk)&_v8voZHZ7J9qRSGk%f9BrE>Fw0lFMQrmjXaxPN4gt2@S2Hva zZ3}pnME~UmSd9gQ`)&ibMAE-}NTZQ^vaCz9gt|L}+9VLuGN)K7vI=TaY9bmdBYNXO zG|4mJz>^2eUjcUhZ+{b>i4&gn|2U4S zo`JH*h2N1g+PxMzLAWvBb?0&CYeI=EQicoKch}PvRamf468a9X`@RY1#+W)mqxHxs zpcfd)l>PMi@8wDvz|)Ak!57W=r3L+fPzUFOdHyTt{Pl2d8rg$cQJfj>6p!@lk#?42 zMugjJ$_8I7*$|rOT0`ou)0DJVJu!xKw#~9MJTXjG0!S-j=9yRQlFmI zuX_8vsDV`TDnMm)?7_6UbK>~j6+avWS-9I2g{jsn87<&-nC(sZ1Utf`L`?!RW_b^P zr9+c|QHOFsxXdPrYd9c98Dmz@H-G8rz!gPY1pTl`ysyGBwqp4hZT?gv&lKY}%zzU2 zrADxu_WB$5F7F&@^te9$ee`;UY^n;-gGBSg%pQL5JBoSPkO<8U60MdclG?RW(LSnk zv^Gg5`BH>~fAvAJQdzo8fB(a<`Kb16_~Hp1pG}g)i4I06pyg)$5b1 zxZC3|%1*`|m65oo`X3Oae;p{gwr+o4%6{1YwiwMv`$QI@OLu>lN^!TpI{5)X_6H5( zZQhm&yLl#PTfihlBH6=FU^(iuKWvpGTX7+wXs7 z4!-_l;?jYD3fBZ(`@7KD$Wl3+lGBY#MIHVxqalC{;R#UCH}NXP+&6k+j~te!WJdwh z-yxfP)dhln(@9#?pYW(lbyCQiv>*u_n4 z+RboD%m2dxw(IKmU;pqJO9P`IWt3)rK0cDXdvI3^6!uwIxSS2IzXnTDts^^cTNaJ1 zirB1*>!~W(FRFJ(KmT0p@U@lgx77l{xs|Sv`0XpfRp#_dd0&6|5AB(mP3t&kuWGuk zK8pOs{`#LSC1>&ba0-I7TJyZ=GBYc%L@CpYZw~>2!mMvPEyPzsRv5WrGTQbhb%!4Y z3>a{?SvGWv?8fGmBj;YP@$aj)PDEZT~H zK8J)B#)y`i%}-p#|IQaQaV&hrAIbxkUkU`a1t-`fXxb!P2l$I%srT1l^?r7LnhR_a zQ0e$JqN#j)@e>@-5=rUWT?(=@o@4cIjr@m6!{J2P^pZ2ovWWWxmi>SKdX0DEmtO74 zm>t3WQ>d2zl`}jbhx;Yhjn>Q8h*z~tfCJd-6BIbQL1C2bLAKwt_uwo3?+5&+NCFbt zI}olY!PLOV9$MF`-~@T2GV1rKB<_~#M;`3=|YJBqRg`1SAFp1iP4; ziBmvAH+p{k8M!grhN$DD>iLf)2n6sGkRlY{w_f>J9gz_{!1urFKF_Ut`+sl#f1iW! zBN63{Umqm(bB@U5|I4ou{Rwm?(oHyZg-EI;>}-+2)~aH#^W-Q;jp}nsu3tUxteQc2 zzuP00{XX|>b{B)(>o}EOje?`0;CnlfTA)I%ojJL+WUx+bQrHS#a+>7KHVG3C)$t?s zlCJ`Ofs8)oHScmZ49|!LpnH?-OkOm?GMZ&-;#2GL#HL5kE+6^nJCV1X!xJ`gowSH# z5ItY6h1dcJ@Kjg_I+=S-$>Vn-n8Qy5RP0n2i>W5p7}xRbxehd~PalnY_GUuyIe1}D zQrRX4-B;3t8eomf{dpp?PF$Wj5M#5Ct+ufFU=^agcZY0-q&VMe1;Hvu>7ry_Je#?u zek_0R>Yt!JbnHctSdQDcc{c7)PV_q)s=S8DhL@5z;l35wnX;2QX&IGik5CVex$IUC zN3i2tI6n+2T@4Wre4k#EAo-o`CXuxUC7&Al-n>REP(~+a_Nq6NlbRvW`7N=GDa;QDyw<~g0~MLBsXQCi_^AHf-~*A_WgFy+Nd} z%sGD~g+ae{5Gqa0;=V#NP&R)+}OB$ORSABY%c;kUYKHk6jh}wa3^Mwd>zHT&zON%sa6#~EYpiN>X1UfrD9c^}|Qiq~@ za{?d@Jg{N=Yo1&28QELQ2xNblY%>TaXf=BL^kUb~h{1`vd&%C|9(`etY^Sh7k{gBS zkl8@^+C1|;%f8T9bs;wB6bR=HQq0JA!B2&>(2xvf^c#25SY|fVvgTp>klsl~&%S(g z;%H7EJ>e=gZ3&Rlz9M9+1wc+R_Vad7(>UA>(Zvoneg0$;Xk+aMPFKlWh{}M61E3*g z-?O7U5O-$oMF!E*BFbeY;77tz_@dAORfSYEK>FMH;px}D%GT(bg?0AkufEYXthrvB z9&lOG>yQTr;~>CA;6)?T$TWOhL_+=Yif1@`C?B&5$Nd{B1$Gn?e5~*X>&7~-w@B}e zw59Jvl?Y{lUE_d9sVPcv9p!uTR8*eEIGyh`*k6B%1%)Mio(~W{ z2tI9ykZpS1cV-*hFGaB_q04j%04Sl`Y%M9?WpV~8Qaa)?ezZrZ5 ziLHx`b{kB9+_dU+a(Q8|WwXjsz0 z^0{yi7S4il-v49ht8m2#)3<$JQSS59_r!)Y35H%WDy+w0eq*|`1S7nl z;3kCwJI)8!V!-L4OCS+>NGGA;-8te!zrE)$lp)9yFHq{>Af})oe^^t9TCPac^*ww4 z@RUR-c2C~)2{@eQt?%}UE0H=}Dg6envf(@ln|XW_1@@!7ZML#x2yg4%ecr!4Mx96< zK{U`xh1@ueIQsK3I44(bjfAZsz8n)I9`&SaCu=F($k(}w=G){;hm;&C5WH4tuci_+ zm7d(LSrcGiV1wA8-B+-uU(oT|Nmq%i!4f#>z^bajqu4dGvlEG8<}83&IGq!tQ^Es2 z;)|_e29)cE%}ewwy>x@GjA@O>3joDrZ|r_j7EL@K#_1~ph#8onLca9pd=sY69i?1X zvto{OG#rA;^_}ed5MC()KH-{TzN6snI2|BGCzV6pad6&$B|VVBAIv|B{AKoXD@=sX z9|QSokJ4a2w!ec@!oF~xoy;VPLvhUNSn33NW0^$fM+-z5+v;1(4~j3Q2q<1qMD%BZ z1F~223Br$BA_dZ8K0*l(-Tr`}4uCEaL!o>cbwN2aj6iCsp|%^#C-5ohXh3TJEEB(+ zHqYDR@*0N9L0?DJ+3s~|A@uT_M3MzI%f6L_$kS84Ss9?k#TR46T;%9=#4&yJm<~tX zBx%gz>DUbCk;^D!AR?`6g}obfG>J!d$lQb@|01a7oz7X;K-e#r^}L>XHPeo2+nE zd(9}?bn^jJ`r=V2LF`3&{~xWJF_WQpXVtQYD+hKT`+wD5p0kH*y_aa@ho(Zxk@~{^ zdKt}T6PW@UrRac$Iyvt!%J{wWP|^od)m%HpHB<9o{n{q;06G-N3jB*MfV-8bxrs*E zhh2sTlQDfUb8?g99Lc`KWD+MktZ^L<52|6T*(Z?APAdK!5X3hXgaEZ zHPKCV(d6rfbA1h26^!1o%};2m37&M6#-+&?zJ&+Ho?No+5lb#PmF^~h3Wycc@Am2V z98Lfx020-TsK;=H%PmLXDbJ2h1Xqbwxsal{ggGDf`CF`Uo+r9Ym)F5B4ZOGJ!x`B$ zbY3Xmwv|qyt5VT)^&x~aI91`|y;NJ_@%m_0%&O|`!e^>lCD4Ha4%rY)!cIhshU6ci zmij{NG)-Y|qgsZ)v&rfDg^R|Ns*_$S24ZEs9fILgyyW}U9}qp?uY5&RmDLOj;#!Rp zxn9BIGjP*E6zjX^ydqzi`9ufH6NVOVr&M@Ve`E{EONLI) zvIC-eVe|(!;|5GII0c)I>u$LI5=c?{rdO^(rXiZL=Y;@vTu%mGsu=V(s}}M}yRUF^ zR$co3Wh{)8!fD=hnk>-#IJaZkty~Mv$)O8tFWF-#A$%`Hj}h@vvFoN)Z$1x;MM%L7 zqwZDoi02LpNM{Uh!9~og>v}6x-3Hrwl=I4^aP#;v>dO2JqJ)}EZ0oz_ z1u5*b0^!A12{jNwm)Vhs=$aT3s_8r9{CT~;NP+|uO^VAx((3Lq1TQ}Cv2BJ^K<!@gR;2 z^P^o5U5LU)Vy+oqnpBfz!0^{2O?w%Q%iy*#cz{Ky52qiifnCKFtj|3e@A|ENy> zXVrTncO?r&Df7V2Os-ds8@*sH|K}y=09o-RghFG>wD10z9L&QuavREQ-xn8FZr|0W zhyk|+l&ri;8tB;cTIT#qU9+k~?hw}o%1&-iXr>^#5Y%4%uM$q^1{5-8%IFIXFs3g* zOMo=x;93rl^k3>vU9I$A`|VNhPnRm^@VowRyPO&Jh2vs?^I!xjB3N9odtqY?Q-zK; z&o4viHc=1Ol^`|h!1{~|)B^1hJUNtxSk@VM^O_@ZC9%&1R#8<>w6I9~ zamXxYG|c;^5+#HZg>FR#Q z9Lun5FieZGp{WbjSny9U)87sU6yS)KEYDip1dBGw@3nmgh~&iwZFj39LiNB9&k;Zb zU${SP8Iw$LMAa%}`kD)i^yI6+X2N3BObN zP@O5SK?9EbOWB|cZ@wGR9MWJVWok|-LZt@>Os@d>HnZ)?as>I=wfdI~GP2?t6Fnj@ zP`cpmSfPrPY%gfDot8)VTrVLl*L+90!lbPgdOm#g<~XRQlk;#&KrhC^jD9yHqaZon zV(8SDjm=o%xRc)E@X7EzsUc6>1kxghEWH~ZbP)PQ(U=@Uht5y`A9Ha%G$KJYjX{^S z<7&ox#7{o-J_!&%hHcGz{83mH1IXViEP_6V!Ag37f7yi}vjc54G@y?LN8X3DUV@a$ z&7qWv&rZ;RkfcW9fEj-`i5rn4IiIckG<$ZqB=YbDn{bY^e##^ocmIY0Be5CZ4l}JK3 z8!c`8vV~IR4$@lR^`wo{C;Oe~(B184md!Wtq2V+KKoSJp1(>dU-H7qx)JJj$rg*;D zuL8NQy0sTL!g)2IDm!tDlU`U{w42g`knZwnHgzo4qRZZ+AP$&>`tYp?Cg84oB3&Ft zzSvW1Y+r|zn@M4<5wEcFOcR_Ka7VlzIQWE5S9F_8L^h(B+-nOk}k z79iH6t#DLRrO{OHzU|g{#1G&}d}0rX<&iP_vfN3#o{4~k9fL)Z-u%+lTsEK}1X?oZ zHES!c=^_HpR&=m0BNDfjWuYfP$zKwIOVNc-0J;r!*69}CaZyH;2uEh0(CLNDM_tle zV%Q~*76M@5hd#Ilbp0{7ZDR9m*jH1m)W@&n!KXC^3>I&zGSM6n6s|x39P12nlS?#L zysSM%5C zHMMOGqX7bhYUmw92)#(?O{A01yMTc7-a9BtCv*tC_l`(Y5D@7dlqyKCQWX>|*k06A z?>Xn*Z@lrp_uV(f8`)#;m08xDYwfw$ntPSG`#-YFkfkk;>w9M=O7n|in-a;?Wg3Mc zp}Y*NPhAdQ4&|S4N!!FL5|~kU_>IY^L8>?t5l?tC4au^F*c`v-AlFLH)=v)+%kg;tK%Un4HwM8jnBC7gRgdWZG62yc^~Xh@u*u4|sC3D>d; zSTzk2Sa)e6uMs%ZQQp{;7~;(6Z4tbpcC~#Aiwmfm00nuhDs}S|ab8#5QOe~v!1mCR zv=%qJXEES{mC)r`(P(vzf}O-H5D(pV3@j!#VY)=ibvvcYC=}n_4!I9#L@?fYh`a89 zP^fh22sI7Nz{LAih{1wo(5e|-O%g?wJ_;(yAbS&rRmC%o z0zulU!Cvh`fz5Du84t7XM?rgeGQdf=Mg>?^V1j8b2Sx_&hQ8IiXSF;UjKz8DlC;hH zv<}^fvn($-%UBRBAy$0bLr6@cdmM!d$mD0!fQ@e_*~Uw^g}R`^UaLPKe`wT>2)64& zk56FY!(U9QHj8~F_{zP)>4AT8p*8JkP;j@lQ`^~SatIkEaHDcLLRqz!lu<1(6<^lO zU=f};IGUGcHTkG$;PgdSHJMz5X7(?Q-hxz)lw5Zoo!m2W7GXlWf;!H#>rtzF`_KgH z^2c=7CtT^mtX1gxi{utN^juRTEQsm!m=}p{=26IJFM;fGpwvc=YLwYKk4@0bbKDyi zqmUJK!uk&;8R}OvM~_hj3%40XWjT*09R?qd^pSH)5{JeMOh4&xA?i+gI10Vw--Qzn zjG5*vjx?|`eXGEiJch>ppYc87@`QL*v|T(tmh5anKTzaX{tJ|@RYydQbPib^D)pPS5#lx@ay`dep=+Yg=C z57NW@LdR1>Tt%K!&{E+XIJ4$huW@hU6>_(I7L1NXq+t8(+{eQqVo@*SI+5xTemTfu zCe_0`T^HXIJ4VZv_@SnZ9EL!f$a=pN4HAb!8K@3Th96~!!SSkbN8p}DV0y{{d3`*& zwRj!7v2@uyipi`=8fPQ;4S=NA11Ba59v>~lS6JttlwYg#nOy&1M= z9GjfNi8$*g>z`#QVhO#e9q}& zJq=7SEux+OMb8~w0w!Wt!!2VW;ihes6Qohn=92+WlGvzvD(A-sI?Y_e6%r|+= zSd7cokC*l$u_f$-xlbp)?(y!2(I2|N!zia91l#!apj0l|YUR0w;sJIljUDCrDV0#! zoc;8Ont08WM^;}NR29kZ=dB+v-YyPOOPGlP24oL&&1pNc%F&iRyOf$>T9>{#C~;* z@k&_#-Z7T@?ifpc&K4>Lg#^e|%z)Tv?Gg_r$U!IzvcT&3#%aQyg0cQQpZ4^j0EmC(I>y^;!r;QTqWGqow*(BQ6#?xG2?}8D|W}I z3Bp4-7j4RBiySaJdbkNDj;{fID|)kWRUQfny)?FKSK6MUi*db*?zBl4EYuoyOIv%W;~Eiak`H z7Yb0yL zlr>~+tEmdNH+^RH;aFWCRn*I&{)!n9=Vo!qg=vDRXx%ctXpaD#gDa$ENEyV_=kDW^ zq8z8Y8ehBnFun&~8o#LxIa;ZHIMOSCM_irc5+&%Y2;1`xqku#p7gA^Y`9Fz!0v!*- z?lstg-70Xe-xe(l6U4F%5?EScg6WjZww4kg!$$korv=nfL1FH?z5>@L2*k+8_|I$~ z#bm^mxJ632lfTTNebpngc-2+>D5a){i0Z&NhQ&=(!t;3eiG|FdsF&O1LAxWR=wuz1VKn=t&`+%&ch8skSMmDsd!>AsY7oaf%y5gC6kvV}<`*$RA%+TfiP zxm!$6Y|XRh*Ncwi!orxlUz`>M>NkJ1EO*MIB^PB#cIeMY_q>MXYE1)XWj88g{=j`V z9Lzp_v*K!~%TyJ&lk0`73oVKyR`F;ev*obNsX?^hHnWR$#iL%()s$ac&P?{$_j}$ClF>LKLMNEb z{Wy$g9Q4lAtfzt&bI+3o#rOu$(de(9}Q+$(7}Rr(`hS1*q^#?ikAj1me2M}&=}HGnc$df*r=~k z0yD%RkBvk9t&(%4n(TOT_-5J5Lwtp3%Y9U8@FIIW9ur4$gwu6%SRYt$(a{SNaBOuo zyDIArs+WB}71n1-or`mGjRidrNCdX|W!|)1nQwf(cW*hC{icig4xU49%!%)Q^o`fu zOsEY3pN`aA#TBO1NOx^VJu$@Dq~Vo*7};pYzESS{@DoLYjr<;usB65gw;JTqs8l9o&7UJa0S1~3X$H-R z`1Ih1ahyKN_aRS9K!n5;4=atBuw-E*)cW)jI}L_O(vTU) z2=aI#6<@m;prQb%bN0|^g!Dmv`+M=pv~V8$FbTzMF4wCMK>R6N><@10vvDg)X;_BX z__;BY9?9`qR@*?@RpNhozIVg!rqv4FU5_IK;WukuqSRFsQ}|4gdQLlcH*`#ysXmln zQ{%2`K+$|-eqt)IMXS~$P<(m>qj<(!cG8gc+9qqJgmekcfqZYdxUC0pU1&HcU#Kk* z%J(SbYJ#pZRAwhH?tq4IBlmh=kjANQ zJKdOyFK@&Gtvkj0tqQ-eAni}ASn8~roui^qym~?!8KXtu3yt*8?tio!CDowS`rbd5 z2Oh@={3O0&*QzwH({sbKfqdQg+@`&s97$%e$b8HGri}FK zP~;ZO3ZlCAVCkXp8)IBKMK7g~Hth}ZBiSM`?iJ0x^RpUrJiud|Y!1K+=X!PlZv5GK z72%;SgMJbj1J4h(mnc)9!2L0EVmxa3Y=qvrf0*Obc=>k!FynG#x^Sut0X3#K?~;MU z6%Tkwjb6q_?7ITv2L!l!%69|-Eo6>No(2dQA_dkG@^VriHA1wy^uT8_Kp)lHZ6hM8 zB|c-+csV9s0}<k>kAe3pSx><;_8yx?AWT}b$tuK*zDT7(P*Kwr zI7+ymn4}(Bc;AKsA96oKO4gD^?|ELO5=HT%nA}bZD~l)CQk0J|&{H`LjZ?z%q4zAI z22Ob}U}K$ZQ7@rb?$osNL5R0eIj$8t0fkL4Ejl`!^2i&wxNQC zV2{lk<5`l}2lXTIexmx@rPRI?cvli<2N1EuP@l<-ZPdeEz`LI1sTZVpw4vecv)&C`v$x1=MvJouf)w*( zuBywwZjJ^k7qoFN;pyNVSQz;CIeuV;P}7tWMk^Poc+A%A$GVWu#8AM(#BG&x6+Jk_ z<`U=laY>F|!rnzrh2hR=`2TDD6cxJruQF2jv-;YD__wAie|i z;0H3DpG$dJP1$^53-LeSE}>a(LLBHy_+$HbJ+B@PZ@f;d5TzxnsUrOHM#IA2lFf>A zp(;Qg@f?byVoJb>q=;(o-0ni9uos4wP*qOB!L#Yg`7&Zjl5uJjGPJ4t;b{>N@*ap) z@jkn38i0#k2lmPT_s$PnPxUem-i2Kny%C~>`^jmKzEd%*lKXYJmHQ3o7qaU)W!X)j zS~(N(fv@wN&*bvcgX+Ydoz7BJr|t?E*OO7L16#T(0A-;5SpMGD8Q46&AGsmqI5s*0 zmf!^u_8s^_FoS@NRYb=K#0fG{JaU^yYO)6EV7wHfo zvrsVI|6D|pNa8g0nm%#uW#BoSf>!go@m+gjywOkMu5HZpISLvp^VF=S`G7=3u)3pN zx)2fLWLxV~JaSdD$Y8i3ftiaOL%r0bQU$4xFFLjW2hgHg)M7PvCCJ9R)`!dISvhi2 zzsHD^T((536qJsOW>VmIv^N&$cP2=orpTXt{D9MmL=lo9r9S9JSQ`Bm>A|U{OvgH# zIT!QAo17B+hTzbJN`?xhU#Cg<6Llm4O&T|9&AydtXK~;{aTuo)?hKVq@-b>_r>f7I zS5T<8uJt1sI-~h78oRQFNzVHnF7Hs0PA3QI`1O#5D+aZnjAm1YG$n{gX;^zw#lt2M z(~efiAyINfs;ms>m=2F^Tv-(7D^()rwR)MiSg~B@1#Re?nyKw8HV7V`QQq6$@yVdr zWJ^Sf_YAw|hiAF+7AH0w{kpY1;cS(PY>rZm@f8D~;O{;$hPGapwr~B~VDT!TiM0Ii zICGWC7ue-=82`9~lihd!#@MQA2p)TY^zJM8V7;UO{TY>+Y}C@M?cT4m5_d19^qr;K zx$n~#8{!%>Gk@yq;nqzEA*uLuSBR4(%(B{_=|Khs)`rZ(@24taHi7KkVU3=Tm&&$+ z83m>BVchJ1PxKdxrwe`~gY5AK{< zD(#U=^@uX>VGi+4rs~)fN?OJJ{Js$~*Kw@l@JFRBjKuIXCM+n0Y=z@NZ<11g>#IaG z=M=_SNWVQlmBlXXnWqY5%6%ZZD6S0XNrxXN4{A6>NB$oXa3V9}jRJ5SyLP9X5nPQcTjPBTQFqE*n}OSmde z=roZP3^5i+8{H`T1{mbEzyt2m5<7`wMF`Eorp3qNIKUuR)$MMsd0f{-rohpxw?1e% zhjp|aAJUArKvO@lpIazwIRBOTgTs9wR#Z%g1SHp6wqKGG&dB=Zu0=oz6Nu}PqVT%z zsVug=oD1!yht=)W%b-y=J8p@wA!Zr|EPmWVL$x|NSPC^s%FBnoZ?`5tP!{O$S3uiGiSH%_G2F+&@Xb+N#_DyT-qF@ahZ}nR{rm{0T z*DptDpCZX2nt2b_yDe?F(jsN>1K9IE^z&2(u;mz}WWkNCfsJcd909VU&bkfTB|+7a zj9m)#AB&vS8&a?n8?~%iIHO2HBH4Lh-jebD%}Wqhk^}oz)0uICqY?^))ou8XL45k) z{`MweF8b}hF)i#e^)yCzATq5;ztrhSd&_L0x@-n0=;r$CYsp}xb9BBKzx>1Phl?!% zxU*Y)+^HN$A0A_JV4{3w#btKx2)ws|If~F!1y6n!Ns{xSe5LZ3LSp3E2qWITA}6kU z6Ae79a!i?pZx z_v43YJ&V1xXFXn{#kRbZ2^R{zDXB!sk$O?J<=d}zkPduacg6$LK}Oz(Nw(&rKpVN% zhj2DoJ)hLeA(I{wE9pMPo|+sy24%&N$eF9z$pTyQ+r?EIYdi!7wE3)F1D`BC~4V^3md1DC;n0JRJ7g)_;;(-tE$`%@-kt_l+dBV-u7!U?WdRaF22j z^wzs!^QO12)nPLHeNbyGpz`Hr7I}9UKx|y8nR$K~NknPQvUlcs8DO(XwjDLEOxysg z=1)@Qp+~cSj5xP>K2@XT_OZ4eFM0J?J~_8PZ@Xkkj<;1wMSMev96>YBD7~(LEy>do zZnW3)gzjtpNo4FfZ+~(t1LJkmJ}|46?Oaan6eB~abD_FMSdirjaZMA|sP_O&cnk;}<- z=A!SvY5=Tzt2MY7#{GdJ1r)fR9A3fA(kp9U%~k2gQYJOr`G(9~6@e7&mhX8izsv2T z*xfdA$X=ZvWLxLKnnyXY=|BVFrm70s(y;2k!!G2tlBAnyr0a-lv%?8d^`MCXyC! z$^^@cgj6HYOLO zA;Y7vni|T){fL11z#rU5u+RXMPI!j)(xuhJv~;CLb$Av#A9b{MlRPF^R^cvp=<#<1 zaIw1K-0-|{ujXS2nd80M{bwmmPs850j5BG31$)$IC|)1DPl^@c!k%kXVjHg_ss|{? zonySuA~79bQkrlvNX_4ac$KWEFn-rCpqS(ZPu7C?B>)>oaH7Cdhlgz!Q7Lr5i#Voa zALVimLVUei9KdsAuVs5E1WaW>V(|<(U_+D8tubIB4M@v>CqAlaDO#>fqWlQH$sB!$ zAi9HN<}kfJz7o-1Us4w3#`JZ#_(06SQwih)kr#ZP0a0{06MG`9N)e3VYa5= z*W?=W9#XO3!e(-f^Gol}uW~^9DT7rmCqTFfoCmgPKs4&4lt&Umbsr?y`KFyO_xYkN zJ_)>52bPw7vT!t(1z0~Q;nYW#zDnlQ3ik8eTVKlYxAp|wz!ES>I%xrBxhop z@jTq!-O2$}I;6-GSD1tWBugEz-Xb_|Lr$cr7sW@LzfIgYO$FxO zgJTmbjDrvLV&!@ITF8hc2AL3+I?^R(mtBBmQ&?}o7{^KhIHrU?sKN21&10w39T|%R z&93sYhi?O=Q^S^vH%|lD9p;`p*s7lY#UH83n$8$t&#rXQ{PDqQg5N^yn`G~?bxu!m#F+ZydYhW? zC+J)MhU!G&p_mP}U(2hNP09%N#B4Sm2dwU;T(6QcM7#u7M&pZ{lOT%YFP@tyA4tUx zcqUYB06>tB+h85j6Z1y>HY?Yotg4q+7z0dQfNj*l#{7*yw4#xyO2Ojur%6)lx&b4! zDDrkbzvGPC8FJb>j45}F`RiB(?KG@z?ANXMKA`Yb?U9n*MRtqk9LB^GhMtS0Ou%2K zd*W!No8F;Q7=R%{CB2@9V?`!c=u&=Q+Nd4Hf-B+_(k_O04?x2!D#kNQ+-4I)T;7ao z=;|=ObR-$Ek0+nt9v-KyYYa1b9hp50Bw@2T2|oR&r6>>;NS#dt*%O{Wc#*p8taCW> z>uu&yil^2fiT7A*zfCiDx5#P}y%H@oURDPUmoY)>3 zhe&+c6DVp&TG1o($dyw()N{u1o~-gY$IU?r+!5R3g4^E!rU6B%*mSq!&8hUFcBovY zS=ny7mW(oy)eZ~og<*#SkE`eLWwi-#l+y8<^{o=}3rYxje=NZa;Cv5S=0&?Xr1gdX*Kv#gDhOl3su&%pOUW-s2$G`a z*5k=|`m!Hp$Mln2rK0R1DMqQ`xT0$= zzsET z>fv&q4I}c)dcsuhHj{;yd7ORiI+eR;Eycu6+v8T~MMlR168GfWRcFHTy>2ro#lY>Q z6DPSFPBY|WRi=bTCTZ{SM)8I>;FiTJhhuykVq1J!M8q?$oS=r%DRYKfrDd_gCK+e8 zH$__P2of0W$wTq&jx`=?l7$G^s^7AKr<$;EDC8LtYKB?%2YJT|w@LxIEVjZ0DM-TE$mLp?fuO#n7ScBjCejfQ9)s76rJ zjjw!YVmlG6ao^Eki`wPp^#exY*{3}g6%NGS&weh^={Nc`_Zb%Z$vf=TG(cB*8SmlN zy(lh@0JPbPRaiwYlG|g>r&C=~giWVXD`9K0WV-)p6zsw#r~6f}YClt>_DMuay&_&> z8@V`|gTyy*=R{oY1>w-rdIRbr6C`sE`7r!DS z+AT}x8CwgxI5^Xo@nD6}Oqfg8I}QZyCydG>%}mm(zn{+ez;9Bnw$`cf>n%%Ba!KnZiDN-cw@!QT2iJjm&N7Y@H!FozIPS0e~M{gYG zpx=W*X;BM=7kV(A_5{&!0{bg*n66cvicpVTnkj}#72<9$W=ZA(FLqjrHgShEDjr}l z%;w*v-KJs7d>Huli^l)PTQj1vA&q>W>E7y(g*P!?3Nb)B%(TYQ76GZPZgy3Y&(KLV z9eosOVx^JA&TdOx_>d;%qa9)qMQUY!t%10GhL3bN~Vcg8td`#27La%Kep_!h%RB4ju)@f*Mn(TCXB|IBgm_W$i+MbB2N#Os%M zEAbHzQrg4kI(n1a0$odRz+KWiSofdK`y%_HaVtXuozb zY-9Z;ezu&An{xY`Z39b6wT75w-y54V9PuvmO#4>Nz0|#VZgDmq5^P#rvN!tUmNO^PkQ@ox462vOU#QF(#vr%24shs&`$dFsp z4)+yep-!YbPF-@uHn*8jw;^AJ*%MDNm^$mpkrt&wF|HVAR34!@CqR9VFPu@6SPN~v zL8mc3pdt~l)w^ZE*4=zC;){Zzp*>B~?2*Xg@ac@R^ zE$LDPoypfU><;f$<<=MyyucUK#A(uL>@`}TA`Cr7}fT2`4fdHX%odK5H280Uq^P8(|BukoLxKpo^s>x5o)hfg!e z8nLwKDT5e8;~7d#!qdIciVnsK;4D=o&3>CJZ7WJVD#_uP&4vT0&swiuQ~q3`)?4GT zTyif*(Wdse#aN+4B=EP*9^HGG%^nU-Mf1Pxw-85$5_c(I!DZfHh(v2lSUh8I&L9yA zKFwN=mkPzvsVK{%M}g5cz;zNLie)SZ@T?Jr(uZ$srtu?z4_O~{ z{Pc795uT86PUU^3JR9BJb0QJ4SE&)v99E;$9GaCzFZ?`)31K&aY$sIL)i@o3Ds;4u zx$x6zTud$#xX2Tg1^k>rh{jBblQ`2Y>=D&;u)`M?_0aLg z&xL8br1YquoB}a3QqgGWQWnkG6AG#5zz1c4`B%pWVO{&!a7b4fP7&NSIQ%P2h3< z?GJCea5>^rP@y~}&)pX7M{KBY$wp{)&^AdkT{#oaZ|I2W*HN^P+T`Il*0*JK zS6$XKW}IREcpWVK(O_sJO>$^N?RC3q`(| zCpPS+U3&Rr$&bKNCC?1@M0EY~K^(vV5p&`*GQ5Sxx@^2=eq*e)I621zGly?m$L3Xi zFQ5Rbe#(uF`!;|DVB9ynagvo>>*)@BwKKx?8V1y<c147upHP$eiugy=1!{9(Fut z6p9NBWejZn&weiBb(I0vWQ6te#z3G;Pj&2wktg` z)63f-x3tMCVy_*HZFXvu=9LhW0NN_}Q1tp1mQhr+Vq+@Ybjqz*gcqkeYk*!CL5hmr z`faHGw^0)>%LCl0q%axv%qeo%%!#mk%f*#b-h1s-#-v3iM?>ZJ^Tb;o@tP>YbSvwT z&zh-NpxP<>p$;KMG|R5a4KOr8wFa~__il@{j+pcpTOr=_uX>-$jdQ;Nh`-nx=U!;N zDJm+*K=-)>QPQ2`PmP`{mPJZ3j4^n-e zXwnA-{{vT7s*0tC5`dE4Xlk8gJYk|Dn*#U-V++ z$sz4PtsHN`ybxaotfj~=DF}O+^)`0sXqsxMTqDl#krMDY({v>dRrwSJGW0QRRNP>{QZSnitj;NYkcQXXDXI|>5@cQ0 zP{Q8y>u^)K;H;d=`Y-Y6m=Gk^Buc6g+H!U792{ck!MArmQxT@<}{`WD9CVxT;vteb3`rWm+%yJclyb+WOOB>!a?K_EFjg zc5e#kJ`x9KF|-;hP}i;M^<860YR2(94jqn+A89*pU``L;FcU%&jSkWWKwy&c5oF%(xt=g7T(#SK1q8Y4}Z- z%gs2lqYIBCHfLKSt=}4bf)g=>s;J+1wrP1<_#oPa$*`fc;>u0~H`8x9@fmkkM%_`k zY=?I|uRTyJmUi<0n9@H)u{nAud^YFwRVI{x)QyzZF$N zn)#A>tENTpDOc|slfIlpA&1wojP;3^F`ro0NlChlV=VagjacFT|L>QYNGkNas}fKe zG_@XM`0{I__M@!y#eBfgSrMr0?IE}5VoMIMcaW8)Z5G>X5NwQwmyX98kMgGFzi<11yBudGO zx=eWYjGYLo?ty&XXZBTRGGnJm2~jf^T3XHipU_v@ z7Cx>{;fncjMiNo$n_9iD>+6oq^cTOPp-ppy-Y2<%b#e|SdUy4|b82KFl<_DUNvwGI zv@5R>w1$UPj5~u(sMA*I^_=aT_Z&6uW#6yd5Iyk_UQDnelAq4)Q+0Le`IB<{A2d3@ z3p9!y)8~5H>$=-Wl~JWv7_sz&?2FF#BC!N_(qada2{1KZ5;54@iw@Jve2 zT`!j!iHK#3!!EYmt$EC~st(iPHKHQ=10Bvo%V4&EkyW;HN(fP({P*c{h>fg8t`$HTPBhQwb>5sBz`{CJch~I*qiGz4k+nvwq-X zw2Z8vO`TuJ3mW>BYy&TNIS~>*-G1X&%I?!i^;g#YMbW2IKUBu3@h z0~(4UJ2O~IpWC$Cy3uc9sU+{YF{gJ0<=C_;eht8a%{r559=!kJJ!Rr7xn4opw zcT27h|L4b=`*-4*%b1Vne#LzF>-XQw&~JcuD&PMvK}Im~WzQ6T;=amb$lhyx1AN6q z9r)MZSGFhdW?bI@>S&n13lfIL9BQs;q&gbOmVwWPM%sRdsJ5WhFetw7>0b`Ej1gCI zuL$7VDNIz{MZQ1kpYhLS`N6LL#=pT_Bj?t%>H~Fj!p2GEh|28;6?VM!X$3w|8hamV<>MU}(%C z2l#uneq});shFPopABgdOKkrq%0Ea~(+Fd!Yf@nw{uGbt5&@U2O3vOvg1!M-b$D+_ z{DJOwG2ekHyIi(6Z8UPAKIHmW&X3-us&^U}E%ja_LFL!~nevaS==+nb1rNcQJ6pC~A*X@pLC=arOenFO0=1hkxbhsjAqF}c*{&y;`A zRwb)EJvc=)h|d`FvIPr6!jeWXSRjU;{GZiJ(OL1otL`(w{q>(|{wlF$0ZKmxvp41- zBj;xt6pZMm{Wl_hWoNd^FnFi9=>tYVn=-!`pd?G+-(i0v`ZnJrpGJ&{ZgCBc{2St5Q9mtE*}NiZ&FPDHvqPTn6w82+<* zKN~@zxdqCvNK=8rjz{p8|11cG{$Ybce6LKoV<*+Jq1D!PK-6IGe-QV>0;Q~#=U&~T z2_U6SUV{CH3jcn;epuk>OYV-hO2Iq>g$WhE-^71r|6zfRtEX!p|C#15`TVp%*Tj_h zzo7n&oWBFa-P%z2oOgH2-rtFu*{YE-0&x9*rY)xLz_fgr1_l8G z|J(NOU*sm|{6qf65H!-}55&OV({hp`vwzP8wh&eC{2i4n*Me!4e_^~!Q1e?F(;T|r z4v11ujPx|PN*CW$EB?f+#@+bNq zK-&M%Sp2Q541#~o@UJ3%fk1vQ>|bd9vB5%E{H+2%f#iS5=O;Wl;NPeR#Ly&zfEY_4 zN3tgqU`B{B#26ripWvUlzIRQ;L04gDI$|{KY!H&=BpZgNfJGr!|9zooF5jEqa_C%j zlZm-EIMx061^>0&q`^>(@%*DKawI+u3JCDLb~zEB90b@5yvC@BzZKg32BYV9RsZ(* zVcUO?`ri5fEf>bE`dc^#_-_hDfr$jpM}~>*jq@WA_-~QG0W7~n0xxcyI_)_DZ(xFf z%W8Xs{0Ie(2^W41oRL8n8eDs{M)l4;_LJB^aPDT;#OY1D5BHmIUtjFv`R_rj{8wuA zf2giF3Z;Ij_uA9L|6#2sQ;KNcy4!=&rxP4(ov46|mNSIeSrmG`M_KD;;5c8Qw)8fd z^hPhTH&gSh1p|$aHnL8#V1RTKk&Q-NFlJV-YsefWD6qdaUxu!Y88fwLNXq z3$`)Zg29|tXlBIq5%TfWn6R08C)(LB{ROly*49&4_?J#sBYjnwi}PLkw~VW0ZJ2t2 z2MT!K04L6^XSa*?g&%B>P-E%SAR5%wJmchxXz+k$5fN{9&AIIOkJ1UdBHu#twNog2 z5oa8YLw+43$SqT?`MDv7}7JYZ0k8c>fN z9PT7|wKQkWeFw#G58|;b`htVY$0iOoPVI{@Sx6rr`dAJ*Qf5e;!kxLL1cfM0@)Lcm zMhOWHLXwE2kG=r}Mj(v9 z)n5ZU;uWI%^_@8&7GzY{#nf=3q&~YlyKSpOW7*Ky<57#9kN%!!A-A}${FrLD^Hcis z7;6G(5blPUVgju)Rp%wF?8|u8?S5u)+qWM;L&(D7m_os|x?tRzb;UDw~~yx&W83sXZFc|TMZ zEi{}tc%i=29G~;O<{WH*0z!8FeqSj-#8S|x$eO5w&hlF=4N-UzW4U1SF>g6sHwY%^ zSYvj!(|{Ljc<<3AKgZ1+EY>RI1{UCk6EGYV(XDLcwaID%G>1uODuy^$mu7eBQokby z`#?A-koU*4L&ExN_$20^>67lm)(kBJ8d+&p(FEL+3UXzk4CZEz3u+bE*gaUhRyS6z zn<#T1T>>yAonit~`n*+Xy>@K>Gc@Hd5i0)=UzpIzZQXtVtGl~@kg^Wd`vS~5{(Ps> z|BV1og|?#5nw#J7y_jvm)~U+Nv-pC=WW2Svp$p?96UE$&(IheK?JI$VkJI|?G_41? z6u0Fp8RRMIp7JQz2G`%cd2_fswcCs{wCPAkBxDhYcCkb?S2#QthmBjk;-DC!WbBks zisEgbs2CEG(*r$fB5op?BOstEfAFCAk|LQXIb<=WRG(8wPC@SR)dJbN{UXVS6-9^} zDfH^2nsuut8vUGNJp`^LWQGOoPkEAYr? zS1CC;ZB?m}Ij$TF?D?dblqPnpVwFOQsU=k$PgF_G8B6-8&Fp5Zv#Z;~1JTdz$lA9D&d`d)cmZr&bI5)2iPdmu`?CN7Dm#WcY?yhBVHlY zRb!`NN9OR=guP*<{2P{rIt z6nMrHOH4{oD_tz>L;a zh>`*A3yOApys578`DXRX=e4}M#^pZ5;aM}ccatepk%QGQxb2{{uc>|Gru}`aJ_@{5 z-i|fyY6i8yXza4#Jv#we#19|0JeJh+Na5=gINSOPGFAuslw&m)+|6KoN*35dGh>{{ zo(8n*YrYgMLY9_{ebAxm^{&bj7}tUS>bxG=Y9X{`G}sd5gG&<`y;zV&VuO?vxlKUS zO=}*WRoTx zhazYgJJrF6`if|lYmQBFHcHmq__ALGWY`HrA2x9w8cQ~78~<*AL?ElR4UgloCa<7I z?rbv=+Fy3Z-PtI|NOti`Fitv`IE1g8Ry`&lm0ky?G}B^!%dSjOfQU?~Go-ProE3-^ z80h5MP(>=6RcQD4dGw1_rfHJJ_iyV=egpK)hKy;#DKM+|-KWp;)O#iJ0%w9Oz|WhG8}o0awZNQQoLdwsDd=(zNn<|5ivNku3gQ-ZuVvQqY=2Yx`~x4-GU#sUae@= z1NK`XT9;BQ-<--BF}cdTXrF6AX~%SyrMBKV_l+V~I`%%5t0fwEA9*B~F;`E9!^q93 zo|Rp8Y5j7>EGuxN=0No5u=*R|wj_&GQk0fS#-qz@tJ(R*d4{JQTNjlqjpv4oI^rV_R0=36&o)0K!r1zPh(APG6#0BkzYtNpRLr=PP%Wp?tCM=0*) zL`880@2W2};yLUP>GRge1-9H&wnL4?IV*!VileEbj6`D8xcbznXkA`)TLV?q+_dSK z5VB`%Nqm~w!sU@-SxW#cdczjEenpb;O`kgJXEBLIlj6G#V|a~Rw&IRfQP`PEMP%i1 zYs?O0Ar3~TpAqgwWeLUPNan6|P%9WcCMe_`HIXKyhnEZhDjjvq#cMyapXzVge3_1l z)y>_cn>SQ{aRsOVEgY7m5?G{+Fp=C_<$jJLw>vPrOO`u<*P#m})v3f;u z1(dh?@Pj!AXj8pk$08J*6ncw8^g+>tSstAK=87m+R4kPMZfG3P--Ksju4iEV?VYr% zY^1dkR6Gs46(kSotqDXmM?NaD2*>J4 zeY|>3-iRe-;eTbms5rzgg4}66KVFwv$l(hUL355bk05oH<-M%5<8t2G&j2=;ln4i}LdBXMJySx?#sK#?|b|`bsh$^CNeiJsiuY_UX5Ug$cj>F$VN4E^#X{iuhs zg5fi$nP{m2!kK_}3KO;a`}2^>_wijZwKju^`aT?Q!S2)l#ok*-#kF+%qK!lF#x=Mk zxNCyDLkRA!!7aGE26rd8JHfq?#v6y=7J>%L>%I5)eP`c$$GPvmG44C(+%ayiKe~IZ zs#(>wdR5Iif4@14g%LF(-8PZV3Ovz`Z8o(u9J;?$NG&mRo6cDbU%juGMTGA+eO7Av z6jQ$-t!-fBqX^q3>X}HN_$9}}ninswI$kFvoy-D(AO*?QtpIsaJwScFeXuN0ywGQ4 zg^LB1a%13EVd!9Zj&GyIwpXaKq0zvAu$8i9yXIrK5kY=hgnE`JV(i6hv$+JyX*1!B zo?=bwJkwt&?i;1@l&C}r99uBLr;3zLIOxtaAYyHgLZ%GJ{Y)3+TEZ&5XAaRd;7iw7 zqp2rDw2xT!6l5m?Ao=WuLI1i>#$-Bg5*SvF3j=#;N8Nx|lgO-4GLPCqFq?^jT>1{L zwKJU|h=uKr3Z~qQ%b1lWXznS<#_F2xX)HEcLge(3OQa8fJ0XM1gUkLSan-5gcW*Kq z%}C3;nwM(@!Te#|?Us+hbYF!JFQzl{9d6+?f;w9Ws5Nsjs6<2720*ZrgA*hH`q)}L zI28yH^%2d5ysSCjpYzu6f0GX?&CCzC>xwxjCnAFs;a z0Qr0t7kt`7X{$HO>_$Ud)Xgi*DM56-?+ZrjA z)VlgWoUmy=0q#guU$+P*HZGR~`5k`)`W6`h_%2)1^`xb3MiW}-NjrQ2lP=X-9#reT zT+dxd6{k4wI|?+5>i4kMSw6LtL{Zj{3y6Dd(dx$zR;F2fvqt%u4NkXGwvN2oz%CAC ziPxkCmDBk!cMiTh8PxHmT-M4am!C;)q`0~r0=v+Wj3O_DudR(TRo)U@H=fX|mI8(O zn{KX1PXGzS%F6cd9noW0ftT=&V_$Ov{I?O;SuaTm(P>?{f_yF z6)N<+`ObAzSOLFQ34GbbG~OpJx-gSL<*$CiCN2ERgWb`aCib#K#j^RVf#<%!W-_Go z{G83rC~QdNk?WHmW0OnV$|c&FNZr8!xpC}u{;NJYLYK=t63yX(6`G%>-tc=SiCE50 z;T_mxG8xaAS$5OKkJPjI8g?FmYiaMkctVF3)4eqHi_M;pWuJ%1eX#~tgj5x2CHd`% zO=#>3J>>a~0)K0b&{5BqTsG7#pFD{Qisn2q8=^SzPV9Y|jT97X>~Pm};62ObKl@PC z$$CP5y>H@j$XX?>nQzZF)cr?wnx;I1+Y>u5g|^n?>PC<$2sx$-_yf%cOoNg){Mojp ziO$Dm#ScmKH=qkQ6rHExh3LwxB#qx1a0pm~(>yYdkpVQo7-U~P!v+@DXxgF1pR6FY zETBP?L8)(4r zA$B{T?~hY`8T+Z^*x6+QX~2LHbc;PBqgh;{X?qT>P)~GM9I-pCsr3e?g_pD$xKy1pQ$+rBbpRI5FS@1?&~k4lgz&IC2h=| zA`+l5$bDs~ur}@5!0b7?l1Y?;Sh)fZD`=<_0@t+cl(c|p^=Vi)CnKK`t+LG%;Jpyp z_ZkLmQ!5tPjm4qr<@V-Mp>EuhvUE-`w7HD!hkL!rPz*k^%5-)%&(vPquOuvv?oo0I z??t6Z%)}>qi3naV6mJP{uvW$bhX(zJ=8y=Z+4%a zNy+_$P3@vNFm+uSQ;~Y*6n~cbC?cj$-@w7&LuF^E`sHMNTEl61@lt8Dd8tEM58Ey2 zui=pl@o1G9$(nvS5vvwE!%3YA8;>4^Pt>9!6i>akI*y3Q?vwXqCM(sRu_HNv)zTWA zw&0uuqN{KhgmA#yJ-U(-s*uHh+q9v+A<@xB+aRS$k21XrxkFl!8MZ-vYqN~flR;T; zJslamLS}+`uBaDF#94&&@ee~heGGLCu8#a`1ttu0I?ZW4JyonM!%>Dhwv(r>Iw}di zXVatRqRAPm&$Y-tErA$xfm-EYlZv)jX3eTu5znF9FObDR7?mObhGkMgHl(dgh#L|K zaje=a0x1M_o3&U)EooZF!WHr7Da$iZRyZ6@^iStY_@sm+ySbOR#xQo9dQLz`h(F@$ zk8w)ZZ6ul_F8Cp{7#uC#vZwjnV3vOD?$t{*<_jd2pH`BIq>#N$dZ+h5X=(4&ppn}8 z55V*LmmCHb4&^Rv2#q605<1&1);Hs)g`O{%0*@9D;@eLfzQsQ5q{Yb;>#Fd*I|KtXx2!L&w$1+bsKZWKUC$Y9RySsdFtgCRCQhP=EMfmS*lyiP)_+! zI!tcrOxa1Jq}BUYds8I03*hS?DrBnx(W~45+4?y#?B2jaR!am6sNSEdAnHS{qY2D0K&xW!n2eHXBfgsoLTZ%*MGCz0l#$=$l>?;u#?Lw zA=zjZl8_b^^X9^SSYDHBcaOeJ$YTUChK1aw=Lv5U%K#5=OoSjomJO?2$`?Z?aFNQy z*BJtF^ZG~9(guNVduO(&e5QTX<>A4BTHi|Yv;|_>5|2P-wdn_14pu2AbCuMIT$RKz zZ437ilZooEih$BO5FmYT41dH2%xf4~$> ztH-C7>oA>jY%E1-C?f4{v3G?_l?~tn2=(IFo!5mFxIqYY%lW~xOp|OPJT8DZKx9@@ zhdQx;XVaT`mRWwgL%Z^>09m{s$`F3Xu( zLnuxmHQg%O7Yeid_d{SJ-7j^=I&}O#!ddq0#W_t^k znbvO-7$&N&Z^mX?&vg_KtBl$M1&nY;afs=~RoQ{w1Qw}5m|kSa>573>i4DovZL37g z1aJDhQTb`>P6e!53eY7+mBx~NfEN}ejQ#98%ttltr7|95C3gmnN~+PqMwtHa1Qs#$ z;**9=yHOu6F4beJ!`$_FA`*jcS9nhzxu{}OsB3V>viP_|w=!0m$XMR`mf>ikL}IeZ z;y`oB4{AjF^i6bSVw*|xm~b&-+2Nk+?Gl0C!0eNcU&LDd?ZIYP37I;Zy&7Sf!RLd| z`E-=-$!m>n%yC5XpeUwGf>UtlV2_7=+}Yw?f22KJX1bC|m>BJoLIJO1QB0o_6G(&& zN})4JfE_p;fE3j+*&~n~yL zB&C|Xi4=(S35!MHYq$NH=9z(k`#pp^WCH4c#oZj6 zfp+|pqq4ItVP&H)&kL_qTHsb9e#(6kR2#xt-Xo*z`T?vN-uW=mWY`q)OUYV+i@s^_ z*99_IBu%;!dw(8BICXu;-;Y&`RL*m$cx>Li@Ip>N;2OmMF&qYG!{<0ti!SBW&@o+hjr?U97DUAoblWv_8`BY%lJ4^C zy8xDe_{gxPR?~cvHhQt93`?X7=s{|~mYWyCWdG>#-jBi3fxcw!^1gI-N>JfG%zZJ? zczQ}`faFLHL063}ne!-*_)KLLdg@PgGUN6SeVdKBY+1R7mnvxFef@#r@p$Ivk#7e! zKveB(x`MrDAILL91L3pq+2VXbW_Z@!rs8$Eocp^x^qeg9kgKEQTB724TrcfQzhIH9 z-jUY3i)t-`Jcav-p+hZr`ldJ#dX_TO1PPTzsCqzE>CEH1Dt*vM0)HOq?pWo@PbvJ) zc@@t#e8Wrnn*G9|1w;kc{?2C0O3`BUs>&x(5K=U$J!b_+?x@|l$J6|uef6X*VzJc~0JW(KAWu^K!iyGdo~2x+J`283-8L~ty;s_{tVxY92H=j&5Oxzc z{#xWBbC8fsCSDI#8yXBKJ)Bpj-Sk%R{8W0Iyom|zHXW(Tv6{UELv;)I9MJD*QN@kf zXU}u9Q>d$YnOrod3n~W^j|342ASBJzkiyQ?CNN_Y-w@^p*>oPSTH-t5*;G?@w#Bu@ zOud<@N9w2lCdCNLf#nKA*NRT}xS~MP4J(Wb%X&s9_~4?=+sCEQS&z- zfUK0hdw=2cx56v>Q+;g{BF>VRM~^F%q)$1TbY3~tDt+Ha3g~*0#Q_fbD$GsdzCI%B z$YiNG`CP5u?H!Get4l*11B_c?SqkU)i=~LEC@m_cXsD)gZKTz0Ne@M(JCOjeuS|Ie zKJ&&5S?NGbDm1@gE}cIjHIDwQ7p$4h#_?nEQ98Q2we773QArxqDLES$cmND2O3%L0pM%qj6{5Q^B^x=4`%K!|x=vE*ZVc{`;d%b|U1XGqGUz6VES5QVfeVcUgtczWechb#tHL?>PMVjQP z(;Zt-X8-W+4km;=$C9wIxd0$D?*xg=*eN zGWqsxU|pm~Q{)i48ssi?^{$&O@T(W*ek<>iZRi7m2l!QcjGi0J>o)@0Q9+dXpT4$c z3CdQp`QlyE9-B6C1rfJ8!lw;q`imsY1nUOzEfu>}4-PVYc8&==piRX@{Uc&%Z*as|+AtQrtBm>X!@+9u zsB@FOI$-9Rku}z60W`Vlc|EC&^LNetE7K3@iCY^JO=L{>7=}@)mA{(EIFXA#Hu`x_ z&6_XcfxUC@bRr?2`Xk{<+tcLQtkmq>%qL^#&EM<-jQh=2a!Gl_3k}~bC)}v;XEKun zK)WC0Tlsj{r`~R-$s{k{IJZiS@4B_6d949A!a*PJG#JH}F)gUa7<twb6J&Uz*D&N z^ZN&EaBmHpEU$lsC+4lXv0eQJXw!uu^XA^t_j>*YR5UiZHSU0Ern9ibQ zYM+on5<%P|Zk1;;5kW|Jb$yU`h>K3^+-RidZ!jv{Qf^Fx-Jb`?ayJO3(#m9_LAH}6 zJfGm0=+r!*^7?q9oteVaPe$Y52$M8?x)^O4nLFip`LVJUv3pIy1k&hK09X8W;m%Rj zY{2Y>()6;qw2oPN==|8RU2tJ*Nr^em(U&Kmhe?7ro%DN0&i(qav z{lO|n0AJ89*EqX*EYqZiBV&73qb%8lv&;J6nY}hbx~?moonLZ5SM@9+Qfs&dXsTg2 z@BHjlGisBPt^mm!Rtlh`QG_vu?sm~vDk>KQ=B+083`@~E$7~d1Qyk&+X*L;dk;O2q zQS8REnNP+d?$CWye9PnK?XAr?JwGLxFvf4-ti3j;CMsvDt~8>1Fbf+hdq;<9<`%)) z&h5-)HWfprw~BD(Zc4-Y38UBhYq1b~i&1jJV0IKYO;M@2g5=P)y#kY+_Ridyu6A@2 z^TjM&dW!z5L|8UvV#XL^5;SH!gti_mA}~~NIf^Bm(o^h?nMj|eQs_5luo1T@!X%1! zSJq{s3)KMGt5IV%FoiyM3_TC(o0M(BOZ@94Jum{8_j}Lkj)IM^F~A%mZmJU>>*=`4QQK^zH*u5ks+PH-K8 zqtZ_A8oWXB&Zx8LAzYDJF{zEFuXe^jC7zrJTcDso9l}@_UUg?|wlB?x(9NrEe81im zs8_N2psY9!>Ad&z;bq$>Ug^DyKB^e2J|!YE&m9z+W^fGthh3Z9JU=z{ZuwY%jfqM} zxQ^a}22w1T@YB*);irWWd>f7sglx#8Wq~SniKM`^>Oe2UN_J>MU!!wy^ivrPK4TH4 zR-WU{UX%#z-lNS+(Ho)x*qad!+3y?ti&cV&4n3KE8*uZtD2D4Q|K=ZMQbmtb1(Qs} zyg7BC)$5delu=IEPZ<#NQBcnP5le&O>RnH6FUq-Mzd?Vua}zjN7VE8$BW=TST~t&n z@@32F6SnX{Penqz3ADX-tJi6s=2E*cKJvrB0> zV5T4gkObJQGu1}|@I~Iv<@)EVaD5pE1v)+ZP~1WaLK383eMalt#F2?Kv8qd;tfw*U zWSU?()6I_DGpx3-QE5bWO>FN%lqYG_Va}!V;r}*dP_ssS=foau0~!v&NT~g|-YuDo zo;)8KKfIXP+x(iKv!dtw#m8WMz83!OYe>LyJ=TWNQjPxF3e+#QmjxR6{)-#>R1dm; z*@v|^1}<2H$q|oSmSC7#(V#@H%>+{vTkSN+9<(S(UO` z*|9aLeQ`OA_?AqnWiRmy-kXxan!nYxb&t()w_9>fon?hV#KP8nnz>I84mU z+y-A1%yoEHG0TxQR*kw-M~Q~c=s6gF@n!%jg<(YwH+YJJ7Dc5qtX1Ob1b0lggJQcn zQouxZBu{57XZh-Xf@)xTt+9KKV239!4iB zZOVg*;26G~U08R(a#G>6`g-EclPWR1WtM4M&24irSAr6HdafO?f9ZyLrTaL$es2JcmsXmpwLhvg~AKxu6}bd zG~_h6ZH;yN7nMh|f)c`c0&Bi|?EA@v(qi}NF1Mgh{=M2^e)dm8`(=o|?#ROLGwSS+ zjDo>+NDne01aFt=>sN?L?b>xL?`zG-S2ft6 zdGC}1(|mTUv6&R{f$%&uF&^d_=IfO%i9(e(RUkQhV~iwT-heSvcU#HE!D&n0`^tsY zV1siyc6;r%nLse!@ndUwgK>j*Lj65tP7A+i=gy6HE$8@$Z5jRuZ%>vA2|g9cB=OBe zXBI=mQdgr_+fyE0qo`|_bouDw@Y9NCc>u+G|2I{WjqX+<5P$;=`kg|3JAp;tq$pUG zu7Z5VL`MV1!x?5dkh3z%R8BI>Ot8}TYKV~~#9Nw#oEkS4-gpMy4u7=@g7TVMFwA4* zHNzpw_o#Y4>MhEw6YK(v;^E_zs^al=vv`G#&k~%*$FM>rPcG`wx@5GN0esoB&hIS* zgy!AB^cbY}C53g?3Lcs-rgHdaV!9GYP-5L1jAfV}Y`@{qqh#HxYkTs|wlXuFlXdDE zhiGD?FVha(>pAa3`vHA-bgP0yH zeU_K?kz)w(Nf9xY-CY?8 z&rMBvokXPYvA~~}O^X=hz{saBt{0Bzxy-fr-E?v$6AN5V*H{}N`O!I{7j+o>;k<9W z*$<;91kD(cI$sw#vXzsXzpH!baMBC*+QBBL7HH5(myMpf_h%PUlwqPg7D(j#v{J$I zNqFbM#@W^fQCi!w9+PvNWb1FLkyw`Kcg!Lv8t|G;s8%Ir$PlJRSUM{Ufj}R;5_o8E zG>lDX;VXtYn^bDZ2scikgl`(f$PIItJS?cnZ-TRrnKndNkIXohIG?f+L|^Wziiw(| zm`(@Ee`n-nP(Ty~4>ipnokUv^Vq?NCjRgyD%^n z3$O)&=>~(!-S~`w;Z@4b1aK(yQjJ;I{pP2Jwg#c9&|M{ASXnu;P0)F8vgj)^m>r0L zsm%Mh#|=Eg{&SN5De9e7TdeF>^So|5VxT*2`0`v3{*N=Bl9 zYj4G>9=VsZ;n+}*V)IBc=07oaS?l|T>nue%4tO#=G>@JV!Zbf8D8Npf zD2}w$-2us>T{(GNU;NA6cY?4=R@|C!ImAHu`0Y*>-2eddn7%V%MtF%Cy_)!j#N}Pa z<;TRW?+=2SNH};j-2TSr;ob-y62Isn*V^=FyK72Q^96|4s2I>Dn0vlZHNA#H)HX7a zi@uy&o%NqJgnk45aQUf3&ua77<(T*kTShkNC|&&FfK1Uw^UH-6W*T_U^P)+eiSv!# zh3ERn#k;QbFZhPce~X#2cK7004b(#6)259tiLLD)q#RvD*yGXr}nWj?(mm2r@F~W zpA905OXRPkq%_hs!Q6z)iRZR0Q_Oh5a;6=vSH$X+&eER9M{(}^0g}(fBmPRZw5`6n zRhWdjMlvl*lis5~whA;%-lG^03??wg`+VwCONE&g;&&n5mf(a~xZSiloqFc^i`S%< z(aWdt%JCNh;+~H8Fr*5~KA?h$l=Tz-BQ$BajA9Bst`z5UA5bZ8TBVRZq#`9*%Gr+}_eTolFn8I+&gjS7Ub)TUavMXWJbfh+ALWp0fvLsY`bx&c?XI>C}?|^=H&*F%mkSX zL%m}9)rr{&t$R2SP63=Qw2m1PU|e}pz8f4##UFa7PT@5C=WiDOpQmxHuW#1qDPTR+ zR^6&a#0wk_Czg|S61)V>y%nl7v>VNd8|?w6v$&?GYO;Iu(HR*8OdhQp5QVl>W|)}C=+xXaooD@4ZTK*mA# zU?)25969su0FnU2$9i`L{L^(04^LFY+*{$wMf$Gb<{EY6WesNs@K^$qqDleK^yqKmvY zC#sb7^709*B}LZUmB|%ki5}{Pa-+2NeS}wDAmjnl4rfcH^uk{iWC^G>4<82S1$W?= znJ1emDqnRlxx;Ji*P!*&o3NblJ9&?<)=k4pZbbvtavBC?0|a3QIMy{wl`ptNSxk6< zTP`8lYCA|$W*5bBP4oB$ve@8fnvI}CQ8GNo9<^e<(OkQ@^d8G7s-oO2Dvyj^0ub|C z3^(E`r3kGfs9ZO~GbX<0fZl$$? z9+N*(ODP=oEr99_c`Z1^?f|DqU%!g?%3GiaGRck>smTdgXbkEZ;4gTyv2F0ExhCs# zs&FL3;TT=g7fU3RDrDHtC#kX%geHG_!`s5*9VuPX7mX(IH#7oQvP0B<(+=fJx1QzJ=3mp}#M=P~?l-H9g`{~iw^SdjE1`lOc(O>Q8oJT&z`_4K{mF6GivoBytKJWU3N! zr0FLN%#I=8$iLXKpPy4@;{tFIT1M(H{j7ov27O^tBY;J9>aB^SEJ z-9ewHRlr&y$R>{&e~FMpTP)eMCs}|gvK@IIGb0{&o`QL zEb^F8Y5q6U-*poHfrF1l7McXT53!Vqo_WKyB%<&y$gk!WXe#{wP?)-g$KQQZ{>-`@ zMtd+f}ga6G1OXHk=2#ITkNCkT)>i@nTb_juomVrbW*O zE=qSNp2UNY*P7&qpfGe9%F-niaju4~Hh$k@qxAY53L}RktApt;FI6fA;}We-KSaKF z{R4zv<9)o%Pj|VB7jP^=i!!3UBEPPaXMC4*ziB)3W*%qt<~INVwyivbi52qd(Y;vy z{mHhB%A76hb{>zMwo7UPMgV#*w?nGdU;ruw-qG~?83tmxJ&WG9m+k}~`+wsV7E9D~ zX~|$MC$X=mny0x}Pf7f&U2NoL&<+uhS=)f4ja{k6pT-e}PH43s9ngK+(ubNYcEyln zK31M@6pn?B9gB_Z+A4CYF1xhP+Y4WEMblkTH%Yj)zf1XwZ6YoY5D*dPP`ogk0Ojrz zlsRZ5rk20lwaRhqS$;ofyG1|xI}?+Fo2+B_!D29TSq((cj(jRI@)379r9|%Tn}Hzl zk#@lsPho5&`s?s6_x+lGpOz?+Fi+v*$lc}+CGq6Q`5<)oWPz~=F;ujDZ**!kdr&9y z>fOvE@YUHa;K_KGt~4PMKE$qP`+b)DX(GR}$eW@f%a$&pMx%unlli(fX8B@N*<9WV zd!E{s=*0zj2PI$!>>;FWerSGSMJ!fk&rLqk{3c^R6?1taia+I(_6AIw;Xz?(MQGv3 zU&8o&aQm$H8&LaSfE=_$4j8i}I2>IFjg1!4@vSIry!LQ{=aSS+nnA1|i!hsS7*!`C zrf!;SVuKP4Wtr>jInz8@KwFux8*#JnXSP)k7bkf)C9uTz%hvlg1g=$a$D-fLFZT_Q!}GxXJV zo4{f96U@r8|Iy`11d7<^xMgjw6)T_2mGQ$~sr&RJMfrP$-NfnZB^wFy*K|l@xpUH?l%O9E*?im) zgV3!7S8^AO>qU6#4-wXlf_|t_R#Sf7G<4vi`%!|wGf_|E^_)2;dX6UifSpprMx~Nv zD^P3u8vwMUB%KnP7n+OcEhoLUn_I;Bp`;u78+*zPZ`J}dr*Ix+ug zFxQLLf3uptxPvcPON|;NIX0W$r(~AU)m`4@nd&w*9zRsLMNR&KjFO zhqJ?*-7P?MhjX>~f$c9wyOn(mzR(lIqnYPtPOo}htoVeZp4Xf&1CA%*?eopw_~;RD z^Yv%QFA6pJ4O(6sor#R@Jt^PGq1eJ7`_a!#D^}p4zs-cbCqI~di{%{N^e0PQRAXMe zD*Dv3=Y1bR)@wJc(9lOwLx=fD%jbyyJPWweCkcc0n~%h4=L!(<#M0Dp@_`GP3dH5F zt({@1P1$bQUb*pLS3%7vFD?`lV`t^&+@Ne?nULrRK)>=?=pI7?Zgv;C>z^$f4iJ?C*{9r+VFHn-bCm7G9mr4PX_2Rxu(MO;D4H|LCo2AEFX&H zInG6x%LM(cZ$b*%P<#<#rgx)LhIwTvWrn;rZz>Qma zDrPXM!=q7F<0t_fqWLVjwxwtS>*p<5#_)cX`r#lY`!SIbp}NjAt{z40IOrtF6$PxWJg#^s)cudVu<;_8l}K8bRy4~@QXP&Y_aA5rY`%* z-zY}k|EBeum^5gwax#VXLRnGBK$hUoUaVxA$(oMXGYfSH;@cEEk8*v$*ys)BsNMA`<9BpH-w!K_+jEkjtA_f4STWU zx^5Df9Zue)MWZN`q?`I(c7{O(F>ZLT$TRB$H4w|1tZ4Ku`~-fL&>K~`q}lSSflWjDvV$s*?BI7ekM#p{xQIt&5LwS zqSZPd#tij_v$R8dSfT*eu7K=QuEI)Rm8|zbd0r%SQTIQX_@iB^{0Xg@2@Z7bK^|1f za+K?W*cfve(OPab5U0VIYp0d9$bT`A1_u_&NeH?p2$oz?wKQ#jY4thP-b!9T;hjG~ z!Fr-zfkqQ?hrM($QbMKh^l#Q9cntj9b8OP$v>Al;BF5vqO_tVE%%B~cRt;UN-6yd^RxUm}2^qF5AQF-a^ zM#8}v#1n}fHZ-g$@t}+=V@@Ej=1aPksR-utY_D08tFy%13ow|?pfF*X2_c7SUR0q4 zSx?prOiz#&;cA6z1V4VN%zh+Y(~CbbOYqIxu)t`cnk?y7mT9!bVzpi?&rG0?`O@Sr z+FJF7B$B3+cAtl3mSfUD5F5eu6QpGeI7ne@oqJ8Y-Ij_kYwKzWH7lGE`ULt1TB>m1 zH%ho7#LYtaI*Z3_4XWp>+K|7v0QAceBzQie20%cEuf)h3O+QZ9~pUhrHm4LI@rZD{9`VA&@0A`guN=IpW}p+ z33voEo>sxmMUX+^L?)ZmqXcKkTQI=JE!J^qYttvNG|zmZXA|9aBOLg#UfP9BEN5j*ydBx$)`!YO*|PUL@Da%oJ(~l_-$Q12U$mrY=mNjpL%dKL^}hvTNXW5% zo=Zqw&29Z#-Ah;r*+D~_rk^+#ovM<15n$eY8i{Pl+sZ%MXl9F(QH1mV@n;#wAAc57 z|5NsF0Lh=7xW_fnilz3(`Yus;2hxjDlv9B{wbUMhQK|DhkwMf>_f`{K8zh;c z3QuSE2r}KgRQ=GpUO8_jxZrLh$s7P^50niI2G-Q*aT{{f_68~puYX;gug#ABi zf9qF~k3a=+FxGV2m?UBBQ*1cl3iU;I;gJ6ZL`85+4$N_-0C!-o#3uR$sPBbXlSHK=lh$nrzM z=jDCw`*~$9)OcWR;<&tne z#L|Kk;F0M~qK#tME7ZiG%Wr#eGT4o(W`A_A8k*3zLQK_+D~-!5U(c(uy@B7>fBiQg z?;|u- z`&%h}P~wE{%Y|A<;ixr>)s;xwmG0KG;nqJtY|~ul$o!+-<3FW=$gK#-ZO-lDbT;q2 z|LQ+YZgK@)58BQDtS2(6n$Ta7=NhsY=2oZo;^0F2k*AYVGk;H5Y_vN&*>H!F24ENpR@rbB|PYHjhB zh$h%fK|vq&z>`dyCO;HqJ31%F?KfaJA{am0qclJsFLvj_0<3MH!Pvu;pE{q&4Y~hh z2s~=bL1DQ>n}ru?N`bRB?(}2V4!@{=gzlPzmLI-Ukck!3!qr~owre*#L7k3*emW-P z|HFC9RPi~>JM;}jVo41ODySA<6=%0qGQe)4(mK>vr}hJdNmwE~YQ_9CzX7c*z;_EH z!djY$dW)ocB#iT8K~H&Snu}nhT7Eiv@yWj5s~`pzD*x@ufFg`7!`AzvX-5r)re!p7 z6u(bFr*2|2^3hJyJx_RCXYPrO2w-!Q12+GkqEsJjm3(Sj>XqImEcZ`#Vl#CI;DJzz zX){+)#!xfPusE>Y#pNTNLKmsce-?AWxC*tQM}`kt&WXE~FX*%y)$DG_#AbFD>FrwizWhV>deYLupb zI+m^FzK;j_QC}HGJT2|K|KIgRK8;;K;6Ud|djA{tui8OB+soIP|Na;BbqU*;YdIXE zpie3y6fOU=qW`{-fB%Y=s(rzk$PfS_8y?a9|Ftv!zMTI*u2k9qT||1T&hD$^rxntk6qA z$V+Yuw;fR++o8@frDaBu84l?b-ttC0rE?3glwPAjr4$I0wH!#;8m`mScY6!_gp{E| zt+tnaDq#e1Snq>dHW4unhDImx>h!syFLLYa@L_zvk*pzh`0IG%@a7+%{(0vA>~{f& zKM$)(TK(7G#3#b4Puh~ zF9!!+P<*7+lY9} z6x@DG_0Uw#q)_HlR>!kxa$ga=uI3XnH3#EmrCX{>xzOt~?wOk37TsMdrqIw8a~s0b zOvyhh{VAVJ#7A(sd&G8R-I@e^GV~7p^fvw1Fb5bfzXzcRd3WYU9vDo$Y&FQ>)E;8Wdb%tNu}V zO>^DlRZip659=#4pU5l7&ZGhrV0<*{?Xj4Ti0l(A#oxnA#W*Cx$58DFuEVG2B8lmN1}zt@$Z4SzW~_L=sH)hCKoW zqZ;lLyr#zd9XI+2lUg7LAboP0hJy``?X66Xi~3t5GPFKh4vI)%>^x}*a;#Q~ySR=V zssrHFvgt1)#0l(D7N5c$s^YI*rPaEGE|4^`i9cLBFZ^> zH^IR-2&jR_uu9w5T^ji{u6li)@Q7@tzX8zaO9E%2gU+7sJ+6 z%b3|-1@K=e+x-=0@Ls_y0ewwUzU;p&UFQ?={g`wI0g~^Xght$xj;6&-H$HRGAfVK< zACTqQD9{`Jx;i|2s>+0tn@y_~gzN8Gx@~ZqX#}XeamcT6-aCne*q$<;dGq0xlurj6 z@?7aKQ@6Pfku7x1kVRthojt1s%&&1}oXX#M>R~q{cYb;4GEokF1`YU&3xKRK1Ejh zx4N_cteX%8t=rJMvY0s3x5K%sQD03#G0@%5yOZc%U!h|0m=NM{(7IeWLW`vLXqc%g zZG!cI-deL^Xv`f-P_p+=RMAFgO2OG~F-k9&s5Nb)^9T)4XPi{BGUgjs&{YZLXGo(3 zntCpx3=W1UmIiz~eS=g`jb=kROHr#o2^@&uf2Lu%^n4^%L%Bi~(3v(^ z0Y~<|3cv8ipz=}hX#?RtjeEfOUPajv7;KBKdMhmy_@J&qmB7{Tdy^WGA{?Ib0)TjA z@O+#TYsQtX7R|oQrJ9JtJN6TAywsVT zy#5S(xpT@h$-cgAO_cqRyKaeVsI!$;8Lp4K8JpjWlWW4}qZ6hLb~Jr3x&2Lk{B}EO ze0K*`EJtS&xw-a!N z&q!!FQCe>}5r;SeQ(PvU$!n<6otDx!QEX#f)wr& z+}+&?2@Zt?hk`(G2@>4h-Q6|8J&-^kuYTSA>+W+;zjM!cf4p)a8yoKA%hd6CRY6gpCQ+Sa{B0 zGMeGd15|NrbMtNE5w6$VzsiwgE;wRhZG9&A4uWYTex3}nPv8mMxP>pR&hN3)E}At$ zR_mN}vJ&y?B=c>EK7r5hcn9F5a50nQ_A_2QHNTIbM+tlDmme>AMi=gdr=x70B&Jnc zl$y#;+s<_?UD~N7^^d2)Z`Zd{M3$du z@zWHnHYA~=y&zNN?}EYgV7^4KDw8}^=9I3XfNz*CO1|1MKFTt-G@PNk_n?1AJR{wb zh`yth968b)|LWpd+JTzQwkWn=rKVD5ytf4@Sg$*Z-G;E-g-LB=`n*TZ))0@Ktq+a? znZ1ZUH;iM!a0Ec7B;h0-Bv*rGJ*Q~=b3$J$lg>0TPTBf9qQkhOlAUE(BUQ{ynjsjh zXY9fd;DZM~z+3*gBooxbjaG|Pj%EC5lKV49LGTG%*`>dwi4X$otpw%rKC|QRN$^$HklkGCe8rVqa-Ym|l zi^)YCQf)%WmS|)^QbJRWCehnbTT_LF;^XGhc385InqJ`zR2M9ShQJuVrx#{bgwzojM*H-e694 zo4cZBkHgB|6NC<3AkwI2O(r>QDx5<_TuRJH8$qcRf^^-`g0-qFE!oSlw~7!)L)9R9 z&gcNk$B$J8h=TBFSv~7!2Q>JhOW})XiyK)g2qIRD;v1|hJ5S)hkjO+ehkDA4m~r4ks@#KQE0}!DI;*k^Ch6%r}1o6gCb9w zP&;AwE1g!VU@MA4mjWR}mh5rSBqGHw)_SYmA1kG?>DEO#&Z{Izb+yl(rt9xjB}w1^ zB4=a=rcED^;F>6=W>DEE;DQ&829j*_O!ysb$HkH-iO| zRtaIdB<6zlP)uDU_WNQ$bB)sslAEZ9jNs?wT&UPXwUX8ekIkrYS_MTfBSh8RLSf`9 z(!^c`?X>oXwu*i)Q(+gmVbxz}m7taqS+gC+d{vDQVk&ge&Mp(rYG>?obg?U!TZq5x9H1+Zc#HO=EjKIX_D%UV7gsGsR5hj(o1Jl^k55OoaO=!MyYa{&;MfuZOd#^CY6-eDEzaS0A=U4hr< zRgK+n9!dqfs;DZYETXNuM@sHL8cmthnIG>0$7j#-XEZ;VQiY%qxLuPR0r?jCrKJ=c zJuoRybUS`%wZIo}QZ;|?(2uddK3bmMJniRPkM_HxeK2RaraRYD%lWy`t|j3Kxe8S> z;I>g9kpl8-T2BRhF_0<*YjDdn5y>ul0T5G=`pKo$)VH3iaqQ6uW`Z7L__FYfG@uN&}VJ=ab-Xw>UclNDgG#27@pQjZf)IiBF_9%67sr%b!luOQ8c<01%<`&dCp z9o9VTN#vv1ETss?zM0Dv5G)clfKsmSzg~T?*LaEy+?DzqSvqGXr({n^TO~u~0+drY zNA^e2F#;d>MsO#%Md8k%jrO|`$O~lGCV6smm#$jo$yx)i^F$>I_&ASlsrS`%rD33l&3Lz8{&3YOi1G5P3N!!r{P&w-Q1AM|m8 zwA7rxUg_uIRR!3lu?SNO%i)EDX^T~(Y09?$rm9ef3cRNjF&wX$X^W-Os;OXB;W;Xn z`_s|)7V75skVtIr_M<%LtVg?!j)SHd1^#8pcQyK2TbFktw3{J#S>8!RM&gX>yx8l! zIr}T;Cefyz1&E*FG4mU-6*IdNhC|MSDa=VWMU)j-d8wE7l^jMSJS#+=5EsK%-(WBq zKy7A4cy(h;=A$VAn;HU6D)6K&QWfwm4H#)5M+`QFC=Ew3W&Oi-!##C&?aKps;w08i zl_8c>F)|a(6et!rxDU2lvmVbCcxhpuYpDa(u@vjDvE0>&N6K=q*>~-iXEFj9vlY~N zOMN^qSsLN;-ORP(B+{1i~eloFLG_KDrQgY@-0EZftWRIIoz1PjO6=oPPX)ID)D{2s^FX=5SRFE z3YJ#?Vo?;mH;v{jDxb2Gghrj?iD!UFclyz3;Ehw)-7na}v1I@=m22frIYOofWh>*a^XONK@* z+{O)RmM~rZ4QOp$10YQBe1N=U7P2ipKZz;g9@||B$^(~NtlaY|HpWP+1O|m5v%MJ_ zwkbrO)4?IYV6GV#@}xJv-_h8|jBO9uTl{X%UOrZ0Ar(-E-Q~{wZTZ7dRQYgniPa zYrZ~hx_ zehK%!=?pr@7vyg0T~|s1Q~o&MMjfxdC&{*yzHY;Ym@BsR<4jM^ktE2WNUig3TNvS~ zWryf3;Jo&zF_K0AdYpYa_nhEtLA1oCa^ngaju`G?t4DLJdzTyUs5ML$i?Lfk8qB&1~ljpH@ydXp+w_p*MDy@8yj+3 z#zyFme1oN4+?$Xw&n9aaE4~Q|i#?^fuqR`FV`nlCj#c3D6}nTDjrcZ^kC-w^Vv zhnq>sqaOxY8x#P&_=_>Z&my`Xb`6=^P-jZ_)dc9>LK$dctzO;;exFm)GN=(t*LXkz zng6Tj35g=20^VU+o+Lrol}LhYQ$^qs3%Ij*$RP@IP>_|iE^|gn1htn4D|sdD65Np-^=Ny{p&a% z<%OntzArKxF-qk}?U)Krr*Y8X@S7^tip0G4g>K%Lp_;$sBH5)~sU!yuOLRcuuQhsB zEl&dCIMCQj{Gxd*&2|^vJjiC3@1;9PRz>OER8D}1mj_N9NZ47c`AREx8wnJql9mzS zTWVom2rmmYkO!q|A}dfTu{(&Yu0Cnj>9o__zyE>(Qe3}BT1_0LZ`SMxg^C)5%&M9y zWlf6Dpl=o;9t@Sw_3+;_*5E6eo&8GdctR9M|oo@`H)YUZcRnGpUa)BDY0+Rzfe=ab*Czl zl5eASezg6@5gxqTUq+e;JC)X$5ys%NwU4K8hdZ7s?h{R3tokiHj+1sCfB!-WR>nZ0a zGxGUZ_4BCUZmmq>Nqn8amI53-9l|1Q{gW^YN-o0&U3{yH)F(=4=tNtMM0LB*YGM8I z&uDBE3CYo+!lzY*xnQwfv7sV4iq``7Qk7*m)|wT88LQuOV(*6%X#-ir?cRSI*)P;j zu@`!|H<@*$7N7ncATR2m5#RLuYP#Jxh3ywsQC$G9f1Q|d&8xR>ZfKv3uIyHoM^h{R zvZzRoIXWLQ(3a(6Ln&p@cAI=9zp4;Hy0ztS+sQP_2f2tWpudvi1N{cbE^autSL|gm0MG+aGs`L;hGs15 z{oyg`d(jYwo)#9D0UUNIZ{>I7`=n)k42_uMO$f&kQ0ZU4n`+54s@G@nI0a!#m{+}$ z5~GsIY>q0mQ;s+5%VQ<#LQSXgwAv@4Y${iGGBy$=CELgde@s+;)&E{e+8CTY#)7vc z(i9!Vk@$+sF2yCb8WzRv-UFo@VYp1oR&uXu=S zXF4@zE1bIdZK}!7CVmT%@iT8nyFv5Gl+r^VDckJWJ;5TjhWH?y9jvlU22RD-&gbu5 zTHoo>KRovYOs4!T;+$h5loye0A+&(0QySi({RY?{WokFp`*3)5Zz1G$h_Y`CPdTjS z9#hD_&D%Y|BdP)igzOd2$XYwF=lVtkqFi)j%?`OERU!Uh$J6wu6wjqqlpk1L-7|&u zDbn1CQjqx5RQ9OsR=9k&s&(>Aj*@I z&~x0dOR$vg;b)oB#(u@agZ5p^?!m2*q66=QqS*_Hj|jaS+B3#Cu(K!KzGOW$bG*F>|vu_vy8=pa76MFy_Prg1LSA^Iawpv zaGy79^+{PYEL+gp1Fus0BYgj_1?c3Xu~Q=r*NIS(`QiJ1=ZlZW;mvkkn+vuigrfbA z1b%s{p9A@XIUN?Z^>hL@%4QckzI4-;bbEizJ$ReFE$2bYOBPxr9k0DipMe1nmxsU3 zK)Y!0BSZ+MH4=pO)a5t{Erd^oZ^#xOLrx}Qj?%V@Y#u+M;Y?i)Q^Ymeq>7L5&KM67 zSD@Va1W$UH8Fq?0Sqgc~jy`5$!u#a-8{koF7dXsI6$}pvBLoY`pjhP^SY>?&d6*ZA zuba9M5YMBiRLm)%yW0SIj$9Bv_moA=!RgdPMtjVO6=NeMe7r=%_^NR?j^UA-@k*7{ zG8;-N$1sgWox6iL=tN5W;{@=-2J_5C_RSG5=_2ga*zEHd^I;KmNVw}uE$^UoX3~aP zGJZ##fMk4jc&-QryEMluBQI^hzVdNDLVv_(CPZul1|NcfDByX5N=AzGqH^h=<`nOH zBfbIYHoO%t&Rka+CTig(J8G|apcfuDMxXGxa?ZhzeBU5`${t$!VRlb!X9YKO9@5ho zcP~hlJO$~CGlVhr+R945?V%*jGHt@W`M3@-j50Z6dsI_-jJ4cjh+P+dVAdUxiJO}n z&nRmi$JhXLuuzbC(H2Nw+FIy3Ew4GjCnV5KS*Jvy zYAx?E8!#b&Goy;Ci7b@C9~O1oZyP>qt5SQ{D;tyrw9zWa@Qbo2&f1bIzDeo6;1-OS z+%HqO3Kj!do&|GKym23ZJ58U0uarSeoR7J^lw2G`qOI}Dsr&+s=FpwoQF%nX_U@Po z-c@>)L-}2pC(@O2WT&(bL;?6<%lTBMrXAYRx0H$J9YbcM{dIUQCFuOSVXX0upGe|) zBRjmE6|KP0r&v2R?nE1CsPz&Ns3t&5R}ynjgcr73I{0Ps5vOEoognYu-o{D{Bv+EX zXC1+uvP42!^7d($Nb+c3878Xo8hqN3CwD6hGV&95FegBcyZo8r0XdSZA`oc*ZjmY+ zslLfc-)4RRKvkTvKgYW6IsU&xh>sZ=8Dcyu63x|E_A;#aCSgXgQYAirty>j5t`^rj zWd(oi9iWzNTYD_u8Ef0un|~+;jX}4;1diB*MATtU@?X2 z5<*CpB{oMQHC;cRH0WQ;8=!~hg5&jSkKBP0qb}8l=HrsXDSxi|T; zAG~U8$rOn~)J4&J9*$$o38m@DM2EH?z-ri=M`S>7ww81f;?k7!L`J(F2Ytg;#jHGq z=R6ff6$9p38Lp>_D0Rp~MiESdbKr~7baAqXmdVm)+G;Q@s!{&ewi0A{^Ru(WGH(;B zQ}&8KSM^*H5i;9ckBu$F?VwT7?>-h<%{z9%soSS5g9x>&MPw05qIN~Ak%1Ou%JjajTnZ0O1Nzg28(=A_|=qWq0v)X3h`3*odzs*jtl}2L?ULI0Hn>^CJn+Mh z)tC+KbDrV2kJhsb;DO(Ojcp)IsB?|C*bcDYeP*M^(@QrnYEA$(JLeS{jydqA#_dn{ ziK5NRF6O05z>RFS=^kul$P)$CeWrQ$&Ac@l74&WG_4-k!i97l*mH4h1LLx?Dh@LIs ziuIEO`Epr;ATim13_na?lWz{59NNw0Z_s03ZEw!k{pO6uy;?T{caM77XhUTOgALT= zAJlUWI>y6isI)tA?@NzgtENJOV1A}7P!6Z&F4c{1Ar5Xwq0US}BQ2FbtkO}p9SN$0 zL?ENW;94|0^Ki z;k@*X$5XWV@0l&hBVDftj7pUH(oXgk)p3<&JK&L-cZh$2Nsb-B3fkC+lKhIDj1Skc z$NeV$M+Die#zE{k{zbJ5qeJepcTnBxOLt`kd3WxZ@S|CzR6f6 zksUxi9tvps@~UP50OR72qDElzM>b4I$AlN8Vv=W$J-B?>jGk#fz zL`3b9%QZ1h*eqy-#3E)m%Tp^vhe+il7-L zm!u$KPgc|#l|!BmgPZ0&=CIF;TAbniRxp@f!01yNM4-$FpWL$ZCqx?SbZGUAY8;^`4jnabXW7M0#W32014JX(6 zc{rYxtrP&(#msOjznh`38M7TC_=D<~+1Px-??{~qH3Ad@;t*+^DG*)uF$z5$6}jk+ zfM?=I?t|~Af$r0(+F;Jl)JNXtaOVo}B>a4o((TFyq`X*`H)RS8UYO@O2-@9zDr^dg zWh6P#Sj_+Xn%-_!j>c_+={e3zg@9yIZB?#UE-()iQvbH(-_-P)>}HmId{n=dvzUM1gO)U@fY4CMSk@X8ZRV)peD9f*y1JmDgZod3% z#!SC}oZylvk~n}jH?kN#e$EIsS&ytgdn%u;)cP>DXF$*8pSs3ZMW1dY@J2fpb=rQR z^Jsx@-m@*1cRV&3Upy%OPbrgekm(}V1N!RSoI;=^bYBF?8%LZ%E~vPUK2H45qVhr$P|CK#dSc4x&}XO>{*iUkU`!MxTR8Z@ zUNuEPw`r2zoLa|fK9ebHAr{rR825}{wlu~3s(vx7w2fjmWS~SZrg3rA!u_a~V2CKsa6K!Q44pv;?HPj+J09<2UAQ$E?+u)DWT;{Qw4sMHax zyr1;ALTXIOM5C8>T4P{0=vX9>=Mb^y6HxE3?O1Jst%$OqLBou~dw>39*TYR&yVZ{~ z5b#w98eijF^GjHUs2YoiHcy}hl)2TG-sU@#)sOV>3t)2gXze*cMV>)Xag3-7&S+7R ztYZ@!w9&(PTh&jan{<8!^5m@_w8RCf-J|~?UINcEnx=VyRo72-)Fs3}KH5BH^D3T8 zKtaglN>sJfwOoqqz%{n5Qrav69>hI(-_pTc1uyGNQ;TFFL@byLn>L1$Oa^L4p^7#d zcCVqO2wGd&I(1P{+JzdiSa-IhdDudwn6s|?)tWa5&W(COYTmB8kbxf!XNeYDFqn>4 zBiP~%iWtG@Fk} zF!%Q*orK_*X0X51ozpf7LY$ZDFppKQJuwUm9XS&&6gtFbd+ ze9#uuZg{(RcqyM`YsAJet*U``Ghw)k(L&YrHnD)DLqW!A?ltjjo)YA0x$}OOqUT>l zh5sDbm0|IS!m^DqoJ?O4x=aH|(35Ne7&|$PnY4FYH?iU(ZOM#GxU71Z+K?Of3mL$h z0v{=Ag zJKU_~Pe`3ZSbf;w6$8Hk9f@BFoF`7l{+74aWk`_WuL<1?LR(X_N3mxV4@@mMM?={@YIkQ4`w2FqLo2kvO|oO^?yyY-kiR7rN^0Uz$w-C>%xX8veJO!AQU8%h}EsdJ>zC04B%o_nzL zV-t*$wGi=BNh^huTvpTVRP1dBiS(|R+oJW_AGzO)!erRSmE=!TnV=l{7u*z=$7y?9 zh-pv7jFaArwZg^M<~A*icfSG5vuN9&oEI`y-{uGY44b`E^gOq`-!UEf4M5!Sq^d~4HL@g?%VvFoW0jpi_zs9nRipEQIwyfv*%16s$xy`v5uR(b4+aaR zjbHw{#Fcy+F8O5sx|rq*jmkkP%0;%p0GLSb6EWXF9ieAlN+#Xr>6SdzE<<7^bGoFp zfs;_j=Rq&%>gn=a>_9eiT8NR^toJU*LPAz$GyJ;_ms}!vk5Oz-6sNZ-wq#%EAV?Ad zd?RIw>=wBoU#VkjNX#6oK$s6W%22mAW>psW^HOS!WqU0%p`y2iz#>pnWOs>jdu(af z{|wBD9EmMkne+N{%EYF^)R!U`ha3(Q>3McE?_21$#5`UB%3{M;v9WId0A|bdlShOL5e+60h6kR* z@zIkn`0;}QI8K#hzu21E>o^K&`}pwayZv*2_AOTY9me6QY!**kx6AYIsOU!4(H z4C1R25LT3zvQ)I=BBiRWxiZu@Pc)+B!bBu5o#lk1xGIDq;nsl<%W$JjWm37cx)|s* z_@gJx;FpP!Od+1yx~6Fl8KZt*T~3NKijfq!>~pz}SgN3F9J=*WOf;6B?_vZyxZFjF^p015W)$N2KZeN`zP4W1zi`LUudVQl1{h3xlIRSR z$z_+V@$%$Y66wzA)j34P_1kzA=zhdqp3UEOy-Q~-N-idwThYpneL0lbARR_^YEi(? zOwy$E%R(WXhjGaKaZAnz6wB-#lX?D;nm(rK&!t2iuJxG8Y0wD}kB*On0p~SM9Ch|v z<-SIOE4Z1`c2r8Kos&SC1Q8S@AtVk4PWnnP4^>;PtZsx)e6j?|ar;1+?doJ5WB|0< z)#}^&R!TXfcgWYotgIKsuD_^5LydBrFbfF9ZyH~*X8+uu3Qf~su;3;GQ{>@e^wf@T z?uiOy^s}-)SFB?iWHX@>zIuw5bc{NNUentErnsQZHXa_(=`6$Y>s^T%xg4*$O1g+` z_icZkZA4ql!^E=BzQl~4(ZKKzUFOMV?qZI0X_A7afacY;-XUEP4>k63c0kIhlNENS zw5dI1cjvPmP8FyokNcJ{H)|Re%bU~9DyUrqjQ5j$?oqP8)T|>J^eemK2HqhAqGkga z=RbCE3N04bAz;SZ5Y0Y(FB0!W3TAEh9wn)=LEgvpY_z6PK0@5gn~8@GTog&FOzP{s z%;n_vM>|9m1b)CF%1~fx?&|ax7Agu(w>vrWhg$*13*N=SL|eK?6mu<`e&_~Co+z$WrJB}bx2A+<@s{wVw7hsX^srGOL0O*jAPoXP zHC1KqW}-rI7vxF2fVojzvxQBmjzv}i4E<}uxo9|4*7(bV?k!%k*f=R~m2_s391Tp4;1t8|DBYq+Cg^fCc(j8zUH-68 zm4W>tI-@b>@=!Al1=8s{UMkCkxqP>pSc_9rTXfGTx>+ebToRrW7ds%i3dHl#_M0zZ zPu!T2wa>W}x#yc0e8M79$`+c<_OtGPU z&}`M7#ac-qm~qjqh^m~Yt%%VtYmX4pNV2X2mCNiue-l)PGEGfp{j&_Yj^pw-;0pmiHyKmzH>@r%G;B!TQ`p>qhvEe2jvVBs6;AV?N8!*dso(WwBl? zzhm2lc7O zy)GVGeE!xZgHOj1uY4p611(@kv(ok zh3plmmu}z$R`vC0uP1HXN$rRb&)j0zh^p@}T$t=3E!vLxM%pCsp9z&4{P;sBI>=ox z6IBi%FIIiq-4ADyIKmtSMaA(PvhjmNx7>U9EJt{RKB_5~KbFLl^z;2KQw&Bh1N7D)kXnC#i6wr zw{LmFlv$XF10_}SFW?;&#Pvf(ti)({lMrW;{Zx;vq7c3FUH^g~6NZ$1FZBoCloi

{iRyygz+}7OhF=m%Qqh01>k6?eorp?8>8}BT|;Xl8fNh1YDN6Bgq&`xSjYn5H1|d z)#P=oM47bU-<-ANrr|~ElCJl<#nih)XhLerpW>@v%@@^gH%G$5&8fKi`ljSwmsyH2u(`$IgqCP6g z%ne&3ioo@wHFeunGEWpnR;e_Txat!)z?ClquM)I6xq{>e>dynCQ#8#PAvukm9d2sU zkyvhXarznpKa8yJ+Fj(DL?V)knhcHCXN-TwGgwp@RA>fS2U0g>*hEV(Ab$YMwhL6? zUcuMym5XN76oDkCw%R#=Wj~0j|tWiKqNe5wkXF~+%fUS zF~MWxuLDZK&E2q_E2X&Edc_&U`;%~?vzusdLN#%UfnV(KQbYQ=(n&Bw&X0YN?M&(0 z<`J=68Pzh1F(vlXF!hkh*|z_dRoAcPa3`dmlgf3IlU%nyKlR5R{H8Vxu^M9g%g8cN zCg$Tt0G=tx4Wn8d-JJ|0af{pDvn>C+N<3h&+dk-nTAy+W-9G|7jo6B+eaQg-t!RQ~ zL&4BJvsJ_;=_{wvcjn%Gazu(~^?36kziyPza$SJQ>oK;wio=6cO%69u5sG!`FUQhY z02gL5s+#(!1>oJXcPMcxYZY9RRs8IF!bH`?=d@U2lPXi>V~n(uko@!?i5W2A)l2EV(mx=22|e<`Cw^KXd+=<{7i}ckgoR4Fa)XiG>{ZzR z@crU&uJIE?x+wi);s5aOy-p?@FO6PN7x$Q1v~yO^56*@}95VShZN`NBbn=`k>>dkJ z8rUZ2il0g1#+Yz zdpObZ;Fbeq5l~~N0y0y*h4=kYj9b=(9P|EC&I1m1HI*C&@tFgeN0vF+JP9Ne6HIr0 zJaCo^jNt9DJZOz%b|7#Se3^$Np0+e1AM0GAfr2I}Ni+2ZBot&|ZtY{mXswrTerw=_ z*}99!i&K9$Tx~At0f&^u2pyl!P}(YFonim_^x0WzjPn)9Lkcu8Cgh8GMJBRd6_jx# z;J(4J(OKzEyG}$wf-B0a=LFv`b*LJ6x|t@LAm%FAW1HzFVjR&A-jwI;NG#gf4N<82 z{QXU!ftG<};&}0+!a#EeA>Bws`kRxumLi&Uy?ujgN$PfE>sy^p3rNNZ;R*gW$tO9d zuOzz(@*%V{I2Rb@pmy=o=Htn)Y<3dZb@%5R=)h9u2aErp30KmC^lN)r^x{?e0-Ft#`i6Ae4YCw z3F5*1y=XLKw4mB)_S%Vu5aAk4DrYXi=vaGDiMCxaqA+ie>1|WUg6Vv}T~PTd_`tLV zoqkX%x2-k1msA(U`8!Xu{wO zDdc~S7|Q^JlycSP;+2)$JXHM$or1wF-llS{SrhCxg;LEjvM{kO^7$Yy_hWWn%2`rx0|0}BLu*Y|ZiBc;!?M#$Gf((XznUH}uAd_cwqU=56 zh2xP*iGQF;OdBd~{0pkIrp9g;CctQJYtok8YZ2(UmlYjT`aw;F>jeKVfYa9-%a*EJ zT@1Dz=Tai$v@e;109@ku#MZ~x4AugKe{3K^aSiFI`3>;$Y6PHVTD`#a)o`6r-&i72 zut*=MgOh})Zq=Pzr#lfwe|&@kVe6KENSKntDXyYnA=R|Va6MEQxach!rXcY{7^*y; zH_%B}8H~8Mrhd1tIaTvw4o4@E(S0=RezJ163h`SC)gkWcPbRATnYgvBV)j87eyWmW z3hHKY;Cqj{rIpQF)#k`2)$qP zL8GE0E;#RPI-~CVh50lgau4_Wd*A~HYb=o~d zE!>jRyvSxa3hkTjcbjMBl=gN4J9r+~pwnR)m@ol2=Odb2*L46ox^!NbC8vwoF z$6fEjYjJQwS+g(wn!c`v(zpQ*R}F7$6!wQFHS_#4#n*wQjVzM^I$J-b-E@bxTJ~V< zmhfCc+O)udgEr~XEO8}n(PT8dD@|G|G)6STuZT>(8qC-qfiU%AMg_+RDmw8SX5AM2 zfeze`zECawGc!^(dJ@0-gp1 zXDF=FjgIc)rX*)WzA1L7g|@lDdnAcWOnknZkuWLT9xz=>)X{1)8-~rry)nQ|{n5ks zK2MD^z;mhynab3nE!td4=pHv(3aGBWF-fHZi?`4Z_LNvY3E{-&) za;3JZg-l@?U3*g+Y}$ z;SYO0+}=B0FjodFvpM@dcJJrp>rvc`s|eVx7_#4h_~c(wQ57btdobublVStx7Qjw- zkgtF-AR;Sv?G!Hl#pIc1uz2w+OlNihtT7DlHn#R7v>&`@RGO0h_kVtIzz65*XLCGt zL4w&-k(}Q{|9a?`W(yJ|@A$keM5?f1?{!>3C4I}9pxUg%-l^g>& zf8=u}`PYjAulr83tE9i1r%4khi#J~2qK9msP2K!y^$h&0nI=#-2NOmF5f!Zma25`D zA1p}8m)c(R<;B|p+$31tvp|B4(}ItOK7ta)Xs$pvF)9Dy{dss;3y%}1ZtXFjSV=>j z!P#9Y0Opyav3JQQ11&224Q{=NgCc{`?H)z6yB6&5?iTUHM?knkWt-sL?b=^z$ z8bSp^^v<59htr7tpPr9E>PWcOS5ChHcH}4$w_{Q?x@IT$kGWdFTQ>TUWLx^2tqpXD zS>%_k^I$c!>FRU_<=nJ@_n!aJxRCQP=_Yp9p69#Xo&5ThbwdL=20!fhY1{ zXv+xq#nZoQPXb?}AKGs49##ca3*?zWXJ92Rbfrg{W5@0_|5yG0Tl+t+i78o7_zw== zRzlqUk$T)Im-8qyZt+rtLtVvWhG|8yu2vG=CI9uhuEX3>5rbgpGS&?}5q_*u6&e!;%W%PrrVEC?*)Y&$w`_v$_f;${BlWLr1_Fx_`jJnKtgo7xBp+;g>xqs zdIfvb=(MFbP4K_WVI=%p=AxSWlHY)@U;g0C#fH)3!UKSCpg;a#w7Gxh%*A#K zPNcdtBJW3{>r+2(xSzk~PANX{ ztB@(;s!}1{X2~3QgkiX3dkv479a;YN09OwllgNcoqR$deu)eMM-PU%|*auvx>zy>M zsX#v4PweYfL?X$y^HIKChn!#NP7_j2*300vbWsEOC=+kn@^s86AJNO}=ArwxS zQ16+EHZT$_Q#ERWjx+|Znf_+`hX2Rc8a+nfL_mSjgaK|MerPyiJ7<3-#CjDUV`k~q z=_@79JPjc-XBe0S0CfVDd^TOA!fCX_pl&@}YsW|b!J`v0)ZJ-L6xuEw)_z`jr{j9p)>8*WBW#H;PZ2Irrpu-gz@TCta+{L5YYSZy(5uZM!o8rw%^UwX5*m8nFMIq-+}VIf1Cxd;$Wa1)S+TIWF-OHJ z5G#4rRM{3}0=IqpxV=BmZe|%!EnTySI4C#+oCspwS%NFq2w_3kH~sP9<6B$!(#^{q z+Rit{Z&5E#W(;uLks(#jaessAUJDnUEm0El5jIMw))5R;X)M7PT z1!n)!eT%l;Xk@NWLutp-U=hqm^I00BOkAu91k=H+|c-t>Gaq&eO24+AYla`GBZXw}0W}C&C}!{(4z+DAq8y^&t5-%ko>?Twmg3|COZgd`oG1 zX))jyx3bCR;U8@)Gz!Mj;uXlXdWE~s;52@&eDlVr z>F4J^de_kL`1VBir>Q2vi+lTj8q_}x@pJYF9$iT&{;#JuEU&FY;n{qvA6VwIJA<`S zmv^;R17R1ED23Zhd+(%QxK#@?S`<&^!qJ=AJ2ZTE%vUfRZE5!%H^?^fC4$cqWlt@h zCKSQXo)WQ@A0s+gbQ*O2YZ6@?8=2+NMPI;D5nPc($dCV}ox{HS98Ls~v(U-SY5W^N zf|i6)9PR(H8HA#KX0`KWB7KA*|3Z>d|BX+!!`{*xQcrC}U@|IlwdTG!YrPp+2BW$N zV;-bHA1StsA$q~6{vtJ^`8|wNCfhc62-U*mZ+R|5O`Ums447M`*H z-+ug$PW;Dj3YVVlARGhGN4fW~dfmVJ^n6lCmByFRZeqOhZ$Bw+_$>MgYceTnc~n3j zEA80i7hyDAt`W1Wf~e-S%5|z^(<@apLt@NrAZHmlsJt?LkWGSM#G8gdlHs%eXQ72D zGKp_ppYsSlk$EnzSMnps3>905yr6SU)mSVGmao?AvL@@+O#m#b7cB)FqAw3?GW!7Zh&ToU{;Q@j-~1vrW86rZE! z+-1_yOxG^Egm|>&_t3g3dyvymk!ksqeq5wlbab5tp~jW#1=nIPCwl()YBI}BMuKYV zAN+QH;cIz?%HvDlYm~|J7*Jdf|F~dK4HaByN8vsqF{&+;!nZ*+ z&1pk$)HL|~9HNUkC)5EbB5_1}N1klk$m1V1Ui~txC&2s+BvJzBIxD?6+!78 zq=^(!KvY0|*Ws+a_q)Cy`#a;D@5dQy=TDL`lKG508Dq}-D))W;+IVTI!saV^8oRd2 z{)Yi@X7RNzF3*Bmoejz~s*F}Dndw#tpudY< zjLAFNidnmJl#&=-vAB)b2$nRkk@0Cig!L{&Ss1PD{YNdcqfSd4@+sMhB+s_~X;z#y zm$OcM^lOXz<2P4KKtb_3H_cT>QY;9E9fCJKy~0?g+!NOB9->JbxjcE;8Lh7;OB3u; z@$r>{1%&VJ%q7Hck^Yc?Kh*$}%G;Yi8T=BCG(EsSzfO=EH27UE0b*ZqmFD=AS&qp5 z128aRHt?Ee(u14(imE8-o!%_PYZQMx9YPG10I!Q?mOpGuIeh#6BKpbYa~d*EGq%R; z;XwB-uHN&-c9`?TtC^@6CIUygHK;Nb~{%}u9}-Y z%`CL7yf6Ok&re-`0JiHo?EL4#?ha_Zk|0wgnVhg$+wzTJly^o-d?tYM;J2dq7>s54 z`?G1MUY#weji-(jo8(JJAGP^f$jWVC(`A)Hr>T+(h=~hoZvfn+inG6N>vXG2SaP{dbs~108g3>d;djyHS(`!|pIQxd9L^Pl)ZfP?{89qkA<)y6t1`8* zZi|jdp0Ebxu4H4&RKFzQLf&vNsrGso;E~$77veekOzFp@1CPX*9-m2W&`mJh8n0mwvmIDvaS^4b> z5(y)+GR-qceihK=OEaYskA7XJ-4Y5Ggz~8HU%FKUnf|?dj@|@ZsfnNG>#7#BGZa~n z>+pbc^D&j-+kfjcteycskPab9Ml<$7jUU?&E2CGH|NMTyWU%&7*iFZ8^{ZQ6!YCte zUcFqPVRlBZz}366@AZ^=BvEp$+u!fU+iVkzhLIl89W!-Bi1r{_`4_A(D=^(VL_XVV^s$CJmACZrv5r1r&^(Yrl>*J@kxQ8!h-tEokN=tAF_C9K$;mmCy((S zPh%X5ixD5Ni2a^(zn}d=7TRpG&)!4gjVUNb-=d+WYlF&q^o1DUo?+1LGoCMdnr+5} zK2$1*HIicIx)|f2n)EzH&A|r;nd$BSZMCIsRMsAxiWg-`Gjb@6bUCP)1+yAE5E z;`sGS2(~2e#n@k2k84W}c>5(VtJlLaXfm0JN&> z@Pu>CYTjE}_~2s>t7hEP@jG~Qr=zUmH^<55i{yq1_$W4(&G%c zT(d6kGYe?yy~;PBCsDi3ne+w499@BU%2eK$uv$g-+5K>~1@@6Fr@8~<%YkbvK=#*& zd!ko94?UF5Hcm@C+=PF5<-f)BlgZ5@J(pzDY*RDy(C>HliI8|v`0b&Qi1Yb*&R_Gs z0Fc*&=noC<4zt`^^S_}lpJKM=HWN|)()LSdEa!0=cm+J&v7^{MoCGLYgQ;O1nZiF& zyyvQRozC)pTR{l|U+RZi4gfq$##D@EIR&rY9IT@gZJ8F$(1V~i1)5(}<+#Bf5_u~# zjFHR(P-~b>8<&HeO+iBJsS(#33+DmbIN$JAEw@8JITR zEX1_$uYpPw^XHM*ZFdW+`edLphxNBrQXvf-fEVbr)7<~F1rU2evy1?zGz+&Eu)3A3 zCLeKPfd!s4O%LsmrSbZK_w3*qC?&;Dc=-`;hiCfL8Dl| zKJ@CfTm<>Yi0oIFf{t~vqx(I-hkj;Qc?QGAj3ovV_bN`^2z$OJ;%}TkB*XcIAkvRA zTV|U*Q;DNr_O9IBpFM6eFV%nsJ-X1)o^Je#lcVN+>#r}VTz$rmm(PDsa9i6? zVc!b9zicqpsC*aT;}e}rM?{3|U5?~{4xnrU6l9f-elHMmh#G- zZ?NAt*tSk+Q2>uvtSoRayG7Ljp!duwK+!Mb!J#`LMrq}crx8qTo4F~XNYHc;O7~~b zpQY0jX*H%AfV;2#MWg00KOVo!ZJ)Wxc`i)-8+E4I*fp-iH(KQ!4+A0_eO8A&;s-MZDY?+myr z9=)mW;9pOB>Qi>=aoI2OEd{bKjhHhVWc#d7L~MHh>O2_x4jGP=ih$=WTsECR~7?|&0DRLH+RMn3kiawtOyhlGBx7vP|sd8&DDcY0#uij=d!qfh!7H7bp>!7Jm>C0|wbm7Q?l zv?wpW)}CX;vo}zfpMN-y*1N=G3L;)vhoJIU#c=A%^`g$V` z;NqkJl371+&(a`!XBqw-cM0z5lvS`(rys8N=<6wSA$L+w2z(4y(vszH^v*IA_+VhAdTphu+GJ^{Skck!YXKKYV39HtR@bjYl=Sl{~olMQ3( zz?5mQI2L{kIENL)f#ByZ-NV_U?mRLzP;5B(YtWZjiAev4{?yc=`d2rIQQ)BsyeIyf z!-tFeg{WrRpV`a5a&8@h5k4#T{%X$pS@!s)nD(|LcOI>t{iWBr&3|GjShRff@1_3@ zJo^{Q`rjhB|4$#mrCkfph=Br?XHI()SVfJ~h4s&#OmOly4Xy$)_Ll_p70}(xq}PeV zJ4z%WH{Hq}H%a!9ygu|@zr&YbfU-N$9PL5|77CZF(@60em0WO7c3xJXwJG}sa`|b- zh+_vky_7^;ZV*k5dMpM!;>pe$S0lc!J7Wgoc!*GJ^Q)7*xr8HpHR zF|Ozf^UA0#`Q$7Dhe#I#B8PU#i3b5?(MX=AF()S8#>v=yF3sY*=erR&3NMr8@Pk}# z*HYwOHvQv=Ssr&9WW>BKzrH3D=W2|Sy|VJ>z^!+M1Y|~=j_thNdEz&nR>6qxH2q=g zS;K&fnO+MTa+x+6IGbQdRwK}yXfkID^J(Ebj6L&;-D(>2A4n0wl~n8IO}mb^;o3yD zfcO||QdD3@UWM0-mFMGU#J~?2`~hnu6O!isA*@lO>&3vm&%2ksWJ~Px4^nq^u_aAn z-VH)f=>Le$&{gyCX)bL0GCv zN@EuK&IFeht~=SL7^x|M71kn$`KV9F&=$7Xo`fc-SWU%yLm+XpH|0UJ0%f=)ThrLE zUdp7ftX;fCw4o8#-J_AGph8Nwy!o@@vNIQAUd-iI2?qQ4mMpqECeI>z|GW-7s+R26 z@QOW=LpIgPvAy|pRdmloCY#5USwnwoLv{g#$s_#(;QoUA0mOdJDI}TWnumI|QDG&b zxO0ME2s^7N_)gnV$S^5FjFVGtTq zw|6RF^nrD^y{A}iv+4v(Ygk}j^K~?B05;p1SE1k~tQlALuSUN!M+0PSP*SjG^yC`o zVbjY?5``{zm6pU{d1261&00IV@Q>iT`udjCbqyXgdBF!8TKV&Txo-l)*y20%X+|k+ zm`$Q9_og!eO`-{BRsza{OJBNjI~H>luU$C(1EJHn{DcAH*+I>n)5k{as1=YKUNB5M z;iWiuT{_6&Q2;evD+{tp+^Op$5vdq|CxiGwMk-uDjAP$l;6A91?&D?18?$^bW4fpL z@dX;JU%Ve>!5NtwmU-grll0XhMbx)*3vJUNY^A<&7=oSNbz(nx3nV;-Yj$ z9{=92nrInMnf=VuSk%C$h$FMIftLJ;d5v+=9%67-1IFuqOJQ<=acMM5^~A~O6f^LO zv!bZH9#2yq{rx*~%rg{?yT!DAXIE4U!6U;J8B{%^jW*IBjA9#jm3*bQ9&=7&Hj2QI zpV8{=W7p{eT5rt1D`H zxz3^AWCti)6MX)frA1=Qt;9t5o5GuuUWjVpszI+ZXpFAUHBpY@wOZv;JZ(+=0k5Ba z+Dj(yaiW0sQCo0^L>nB76oiuKniw^vg|G>8PR!|6MI?i$=)FgO6=?BJg)aL*+gUw> zOo15sWc}xA*dmA*jHtuJv>zb-@ru^9>&bw3kaoPz&tsk*9`0wLd4H3MH;Hd-BnQm( zbbwz0H3UExr~Cb!KohBJ^AgN0MTNP^>#afFHpur$9fhDENgN6w)*Nx?w{@JNcvc8S zjJGKf_QtGg%<Jq&tE0)NCZOJtAy z4h*ZT#7i3vUpia^W1e_)boJQeW0%A1l3rA$-pE;IvnqKf=$8A$|5*QU#NOz&&$(+P zJyLFrXWkb@)+`TOv~FUx8;D8q8~sCK0kzFC+k{SdiBGx14=zbp&PMN=bx7dREugAi zbBNPSg*yt}LKO{_3Q7DL%AF z=EH5gwNIl+Y19XcYk%LWfj@6IWN3-xfCS3$9qA~hr}ppd%VL-DP@-o+Z^^I?CG^=lIaVh^=n)Hjeq|d7m-2l!T2$=)tAz#@W{e#$mQ9fRShpBWgr+_ zaarHf*7rfyA~Eag>jSl?XLGCN_H>1GjuM!22{|V1wt1kjw?OBs_?yYMwL16nQaA#B z@%1;E&zIpUjr=+#es5I`%*sJgqMWZUh4CuIr+t|hVgJ2vIT)3vI==#J{*+?VNwwP- zmNbtUPoM0V-10b2`-tAQt)#3epJUNr_c9vP(^X16q?jQ_(LKg2t#M~@d^G0ygy40w z8oe1@h`oZ+Onp0UBwTsfb7&bcAUI8qC3Mq-j59R&^oSmu}!*pzDF%5$0I9r6z(>Rjl8KjtE4ddM-| z81Bc@=;5~<#cmk?7Up8XB3sy~w-U?Z`kt)yLpZ6}bG)ll13}c)<9lVInWw|?HMkWq zdSzA;#%Pff4X%b{jiw1VB0+MZUWMT9gYxBTWWEE?ReZA}Hzoa*l$5@hq$`z%#t)%> z**51DPc)+I>1^>T-xh}BI>yn6am5wO-1vK$P`GLrtE&26J>7Gh=9wuz0!DVGjjS-c zeg@kXVS7X|zs*5!kA%sAZpM*8J3FDw0V|*jZ^2f6bZurgKemHZ?-vEd67Gyz~UJeLKwqx?8OK#2A{yQ z_B$c)_1dy_7EMV}gxpxfnj6=}rlb6b=Hye^cGb;3jK7fMv`%Qe zex=4^o68pRy-qQ-%GWFuHp;A~s{Xqe1D4eq2mUXdu4DdazAyM|F{a8MI7L(f+oj`% zs-6&yv`#wuAo+gCMMnFgd)JpXzj@-~hL^h>7%vP(YcKM4(`x7rIlfPi(u4n5ngmfO zGTIbSc}!6c^zXW6Umrn;>tTB)RsprI?2YumZEt$CeD!5yaIo3WfeFoJKp3rm3wFwc z6(9gYWeg75SMS*FwU48^y*c05?bJ@Wpm8cM`IUaCXCik2UkFpPm>eSGS-}*44kgdZ zT~TSj^BF0Yc-BKAjH(OI@&>f|$)b&K#%|Qvtp5c_c9|Po1nkY29vo3B!xgzgQyLFk zgMV`Qo0wdvlD9M`4A zrfsqdb7v*yLXT>5t~On8EROjoO;gLthAZO%kDCS7-CA=H_Xq?|ZN zSKahikcuAGb|%?`akRfqoV@UNBw4FRa$H88Z#nDX5LHYGi;fz#e5|tl&3cwzw_uzQLAt9cqy%wiz=)`XF9)# z`{ita^%!UZp{Y`JrlSmv%uo3JdhIll8hS-VRz?98Yjh=jrij6~=#>9DuKx0u3jD!H zj%9%CXD&mBi#Ie|sD1ae*q#cCr?j*I{KD65^vjEv>Cztu#t7*=RzRm2K|gWCy=zIV6s2TS?u+O#Au>=%k*F0q z#v^6GEdX7MM2om06ZL~;ZrX;fY=c4pLp+*6k1P_0} zrY`XlN1u*v8Fi&WmPMO26WD0ZxSFyiQc?fL5L{){1)>`$na#FgehOhzzeZ1WbW^Za z`)FVsQ!ZnL0wiXzZ3P!Q?J{hA$-w&o0Q%km1=QCDO>;&`63Ke6-b6!q2@Je^HvMUY zLsfiXY`_i_E1^qA%^IcMuZuAJ`YX(>20U$2nYRP!*Y^-*w9p|&hV`wSH|qQBZ86c$ zMZbmq$P#4ddiC#t-%gInwk=YWLh3Q;x~G( zmZyBDI9{d41Ig==PT-q2!cCbK+Z&KIg}}jxCd-FPsKqiQGRFj|r@#O~`5zXouyspI zM!T%Y1@iVuLHzN=AnS8aY@q0%{BJ45t4QwWddFE>CoIYlY3z1o63KiQNa7~XWnU^9 zMW#>T2nrM-cNe}ff<8XZxeH8rQq%b9<|_fF+)vJc`5)&#{YKfWzTh{$4vcQ+Tn9ah zt-W;d$(L{8&(J-dHwm(kf{*_KZ4tXfItmc`Vue!!hvN|F23o(qe1BaF+41gF1*9p{iR{vP?f)kn< zH)?|Iyc^QcdCRE}Bl%3I-$|hg-k(usO_2B?`JVVh&IG0j8=2q`mZCstAFDpx9{hvb z>9!v0YrnIl6J-on&4R*zA6}T7Jez(BY7JFbabqMH8>2HtN^h*h{5nKvpnus+jOEMIn( zIM-twuBUQBE-$*q+ElJ~VvH|W5(#@ARFik-8Ys;b}P%7tdjVS zqed*iA8Be~D8ln0^Qj7S6z#tO`a+4XZ)b$$a2L~}H?;0|ip>Pa>pG2GJ|yE@T6lYI zA0;RN=AR@V^OB3bn>2iRl+;B83$EH7F&Ma<&CLolu%Qb|GK`kv`RG-!D&7&@Ih_3` zgksbT=FMJNAt*sB2{mAC$mTcGn9Ca;_>{Mm4MDRlmm^<7C((Z02HUT*9B3$f#oK)6 z?4a;T0o|yT{)^im9Wksaq-Pvd`=-mo_-GOCK)*E;knTQpkA1uE1J!JQfT%^gVtI9R z9E3Hs(3M=sjH%UIFa>s&&AoJ2uV-hykHV$s_`zCCV$1KEMxKx9aqj!)wV&!lSBltZ z;xRI+Jz;NJ%)-eNSG-DP-Y_bDVU?gs=o@)TKSF^?@XxSfyg{$&9@u$j3&H`D)Du@{ z);D0=h!jkYeL*l_!wY`S2`u|9CD~BIDqZe^m7ouix)uw%TFF&6u!N> z2Ih=hY_px(*~2tNuvSB_UmJlfrJGtjNMz|e3u=Y1YlO^Oz2J|wJ3mbqhfxsLB{z8W;QQy?6rY;xTXsPC4Au;3=j_?e>etZn@cIVh_HScen2wU7A$pdONK9 zV3_HtxvpHc!>2OX*28m@u%8Es$Ju?rjbG=If#+|9VkdpA3R}a2`W$Z~tiI%~{cisA!tuf}2BxG9`FH!ZgZr;LFjr|*KtXQ*ug-^npMtrK374E7c zd!!WADOTD3aII4;5F4)3vkow~ixbQ(apfGGH_01#y`2(E=f8P*mKhc^-DWJS*XI-a z_zyr?*SE*rH!=ALB)uVcHWc`1b!EAx^P9cOXy*A6JOftCRqRbWcwgFB54TS+tTD&2 zMsvK`1eS8BVsTB@r7yRSN6CrRiGb<~{=F}onOwq4{f+Uq6Vh6}WXa}%b=E5}0M4_8 zue3%9hkMqFtg6JHeua&Z@B} z@_0gYqiMK;oNYw;xgI4$hW#{ftf+U*Ho0$|m<8(Xl>07gB1e}-V<+S>Dy|})qQefJ;qXS}z>cjNPB8X~bY-?hVySko4bWEUsVzKk0`Z;{ zIV1;^7$cQ98L&zH7A*({RLPr!V);vj2+K9ZlDO43GQ)IwSiAuHp4CeKA|Nk8o5kvd zF)cZ(y^y_CMxO0Cc$P1rF7bxSV|Kjd`mJQ{K_7GO3;0><9!3Rt3xEk2Vtf*c$|xTx z30Uyjo+BgF6LH2>4I=L7dvk&322Gw6T~Fr8ygOFQYS$3t`(Wf70qxId>?;h}doI4X*qB*L~7}32BML@E!Qa z7z*cbjz;W(TIl5?LR1qQ%&c0chR#zuXW&+`S#2j+7@!d&J* ze8nrYZ>pq68zTyR$em;>XGQA+n~fo#ath$32i;|;+ZbBFqVsry;0CO^(^t)Y>xwG87lJOVS1A9cnMWhL?+{T zWR*Du*FXKLe6fe;XWpIl{PJZRa2jlv{&C{-i~e<>)9R!ZTgvHXJ4LukN@{>3YfK+& zly5=_U7$2Z7;jP+!o`ZkQ@jBTpPIe6vwr(4i`o{+ZTKKl@l2ugajHsNM=!d(WheSC z-uW@K)w`b;$U%yg#TS7YpksWGQ}n#eDnp6`*=e`BfQXW8PW=kF;qS^SL{@pCaJ>C* z;PLEg$R##rdJY6&4omNCF$}w4=6MtCZxwIJO)|!mhGzW)6fs;i=RF6!OeL@Z1AYk05y$$ajORGNxY~k-h{i~JDw$KVGdh7xQtPfOFAFiB@@{bM`__Xr zPFFtjQ6(0rxm7W)D1_?%CS2ZcPWbfw&+<)GcP`(N&k)+Pg39wt-5osehKoWso%{d+ zSPW+r=#w!H>*CC`U`ymevfGVeGnbd!7 zB1kqHKDI`w)LUsyj~=I`x4SXKhR8f+?dB1k3AqwnjJ9%MJ&7N+Ditih+Ia@TvNLws zJ;H~pRUWGiJA8C+|J>)?3X$=c8Ed%o^sBVk%-Db6F#GDMS4P8RA~XwetRJI%#8vB^ z2=PnSc-M#ZoMzcF^DqLj^WT3p8Cmi`1l0g?59~kjfJjYtY3uGbH)zP~=wJ8p6ASK8 zR$O#(QKCP|&oJREdnJn3--AELzbW;r2k<(FO_^=Q-UJC}4O&R@n~hk;UDjZ+X32J- z%86k2*Ng44qGFUqFkdmEjm#h>Wzue#A3`4vSgOo%R-y|CYA8WE5T)&tUy9z6idFTk zTV)FTL*%T+vxaY06Ood{P|}9iq6_R&)i6vH+5Nn)_*q>^H3PbwP&zo;0~U=18-Gv- zy#B)s;3EO{V)w+|WOOLXBJWg;4A?y<$y)SET~FUIkh%<<&Da*s1*8KFXNflL@rVq4 zqyd43FG)Js=g;UF#RF5y$x%V*(U>^lDyM!UUFd7!-v17)jiRpR>Ts=GZ=dKLM^YJd znY?$#2Vh8bpr!gpEpJ|HC%tAy%H+NtE|!68b(`4n#zMHMmM$gG!KO({fovtISLqn{ z?v^Q=g+-gn7>?n2J&FBU^XO!ph$KS?T`|S`Sbg@=ATd9>$d~cR;h<^Ls3I(R?P| ztpKoy%Tr)OW@HNL3*a!><3<7eu%(+pome?K`MJ(mg;*5@-poa5D}c_B;G=`p%l{Ik z;CDG1wf=hbVq|kTn)%^NQWMARY-nVgS~Sj%enWP*j>ZCmfWDHhZ9~BJkzj}!gLWMA z?2hg+6aVNd?iK2pk{2i8X=cvUXhr=o6g@Hg)^B;1gSDw@RSu!|Occ&heta>?{y-fK z&@*Ojig@X|R$TdGySe`cs?!h=x8up@l>eH9)%xY0* z0qtkxkA2PbN(}s**`jUsCq;hK9~TX-b`nYrFPc7!>J65jJ=>Uu-UMeGHf^4qZCJmf zY_=D?3_pi3pK-}7T(qtP2C{!klZAZ`?rBCWgK+GVmMjCEs&H((&6oFrTS{BGKT8_KiWbg+~=lU zotx-)z=NswhxHQTeYzJ63?S~kwx6qoLQrFFZ{vgFhCrMGJ-vWjq9(h+f*2UIdzK4d zYwKMaG@cil(8ST#oH}-_;UCb6&Qf?RBiyUUD_p7#NIaom_zQ$qyf0XbbrZN~PwLmY zHwrAGm{b=$uby{+%p(Xrk|F~&$lwv4OPf%qTM z%#12Eo@#SpC%sj{h6`_G8I*nSfJ@pn#s+eqTM-RetzDd4m;JvEV#O)^tS+Unb-b`B zxlCKVbHY_Tjif9kZOrmRrvJU53vbxqI{>2r#?WqaZxqQv#w%hY@KVysDxpb4eS-kI z_2PjcA#`5D-2GO*-kdSrJc5BYbXc|kKui&i%OG;{*1+miCjd^-Wr_-KEW%|{$xVga zY)2dja!AsyFvqjKQNduvH$d3Ed8B36aqrDx*JR^1T^egylXy$Qywk&Kemy{3h3uyf zm~&8s{_k#b`lwTWz>vLHwKqtpO8*eCK)tX<0Ovo*l&><|=`%2cz{37!?JtzGC;!UA z&?ztSDaEWr8H0^WfieBxa>Q-=6Q#=J09T+f<&ZH0et0AOfv>F)Z5sG?otfvs71u4p z>DCdKM*1Nu<&g!npS!|6Aghq^V0*XlanGJ`UUdd#oXa*<@iAeNJ!j>5|2ah5{V((G zKjD+ub5G+xG5NDGizjmA--%A|Donw$z!CEn@ArZKGq+jSbcs=qCS_gsjJZ|%nK963 ziMpri_-G!UH5`m<49b|M!8fO1D-VhtK=9WEu^ySR-BtWACMhkI8$7mG%;ezq(nZoU z`A#&gV*T+m%=7~~Ucc4myl;vjgyILFb)Qk=fj8u7ABAYs*=k#65FUM)mt`W2oSN;p zRW?dj{o3uB1=3yAME?21u}7Gu(3i<@=648xGPa~XBQ}enHn^*cf*;Kio(*1g^&uc% zPxOyeylW-tw|I=UY}=e&+;hHK3j5`xxtWL8wD3*@Ud+(xQx6a!RhgD_f{lURltgcdy6h~8LW0vpDA?~Ykoo8CWO8JLd zPn|+sYwT7gsH&?x{L+4JJFqopet%cryJsf>LTd8WY(5{#>oG_3LiYKR|NBjDA;w*} zW3J-+w`}msKD1Ok<1lxj$d(0w>U%#`P8R`a2mHsw!@a$)@{z|fUIbyagt*8xg3+e| zo{#{?Ox4X#3Q&z_hmzXxr^&NYd0*)AW506V##t3NZGyUMG)Pn&(mp??;>HXeGDV3d za&#`V?_UF!*?1phJrjB5X=Uu}WrO>2 znk}UlMXol_m`)H`HDl%g%p>y!s>R=fnG`BBP_o{?vI}=|TEZ}F{Isjnjc~B#(AM9? zO;RU^amn=v8)sV7#w%H=_S5h6af0_C-ujg`O?H?78D>aJc>}pc#Rjzq%@g^YUlsqM z597w_WkeLFGrpV-eVc?I2pn|%mV$X~u??^3uz=}pGB9Y=rApsd;i31CY6!D%`bmm4hIgIsy?lpwsX>9xfT9rHe=!q}LfMG)S&m0MC;if}FvZ=t9XA(6 zy*|=;me1o`xS=w1A13fG?El2mv#IZmzX{DUNC?{6tT9A-8e7h?l)#9#CH8zlAiy1L zmv1Wcnm0z4SI1i$S(tMw$R+N{Fs>IvV?-6Sz+ZJAy;P2Aeh#SO2=~t;KG0%FtN4hf zCbt_W!cHWTx3`#hLKsbnC1{b6veyt(g9cj-zi<;*?(;NU8wS8RF1#Xwrlf5Z7q{G} zqrzzLXP0er4|?Q4kEcjp`q^KJOvD?6eV=}MA6Kr%^-hf#s`f1X>-GGA$y($y4W1CB z1HB9Y3751AG9{$NKOZs__V*ffQs>xsL<2chGwr2{b|#`GK+s6OM8u%~ z0bo4oJ@Cy)60T3t`UY~sMHdplYHALpCpM2ErM5IUhY#S^94ZReno@f$u&{)d0~S`~ zG*eGm%LcV#lr^OYhm!p>?|$Py4=M2rGbGiQ(I@?lj|^H37iY_vdnbk(RO@&su8B_4 z&;J1!r>F9$I0&70_?&WzG0!;%zZds!&qv(8d1`~c6sXTo+&K8@hP61t9=^0Va$=0d zAwdId8&}FW8Kl7_2~$bt2H-auLw?3U!l7HTS@JmCUrHh9Bzjb7d*_?$w%v)-bPU;m zXtv2^PN5XLZ);Ah-jWyA>>#t0;@Hf0j=K2H4Vs-@p8}6?GP_6bDP>e0vn9nI5r3J_bbzI@m+WB~f0k8ElS~BA z=v>PC#Sw#PphPLoh&Q)u^JK!~C|Z6)wXJLB$|3ihgDGrX&ulKr)4eU_#KI=AKWv0S zhI4U3sfx;i3VBDTJs-X3+e&GNm0SyqmcDl#S$lld5~bu9Nt$)_iUonJek7ntC7?RZ zD57V>WNN4dFUCt5K7amty1LxZhii4y=Y*;N#m>e+iU_xpITGBsgyMzo2mF%C!rN8@ zN=W%~=9XDXNN3-Ph?8y($>7>ESoHBnAr@g>iR}!NWB?|tzd)SFLjsP5qk}?_P<8PvfGw!>QJDw)<4{eU$24tu4t!xEt7weZ?wT?jZaA#$1+(%{&{Wqv2iTB9uIvjQGShTU> zp%)rGR`%mNv%#KpjpIa^)y3=-3j=;$l@(~UK%vE# zmh*HS7D+ZqTCEn!W&+^B%eT_>4V^>{z)_j~K}S$#O-Q-26#}_A?gA$x zdN_(+L;)DLkpajwoN*9-bis)HRu5IugoQU_5;amax?`qT(*TYhERRPU9?YRyGwDvF zTq>dWso`NgwRZu(eFI)dB&7eJoPsRE<=D|>We@jHO%K^MVwCC1`c(f+wv&-n__N-| zK?&nxz=wYsndrUuj2yXQ%NUTMMY{~_#hZSQJpThhxmY!#*+q+;as0bdf&Tzp4Ag=! zY;1Vz;4-9m#z`@Jzh!@6yKgo-CT1mH(eB5p^85pk1TV18BI`$^3?Hn2-NIhQ)4tKu z8(joll#8xwYoeC%6=d)T#JrXrgV%tNZNV`g>kaw%2y*M-SId zug;6kbzrQuU)I7M)6#IWilHIPq*#sYq{*)DKrxLDVC%|2v=^rBX_LS2?J~LsQy~J~ zhiA+^pEpxTYRUh2|WQo`OE7+!lFsn&oo z@|1ZcsVszUEUixkF-#E9cjN^b(+5Echq^$Yr=gnW6;R+_!`&apV1`!Q2G zX$04(IQl)WH`et9BD93d|GFg)LA#IUg{;VWx(?Oa6Xh7?WqyJGMwQH=xo2D9ZB%zz zbIMHQy1eCLLSygl0T_UWv5YTW3u<2{HAPtah*GC*8lo^6PIsTig`n==>Cm~()i7g; zaa=)^=k-=a__9JL0q4$n;+`GUh`|++spp0OSjNxi6+t5cHXS@Lc zZSMCW%_V?r;Iu!0F{eHrDu;v7S?}1}Uy;fz?02DE@`qY5fUop5r*;d$OM9*@kb#!P`dZQ77L zgX-p6xeZ4ZQ@bp%hCcdIwHuz}Ifc9<=Eu*+|5(OzIwV{|004#n)ar1gOa;jxPx+<8 z+91~d>J5|5SE(L_Rdx3v-x4zoYAhqh>UsLlgO_EEc^K6M!`5fVN%S7}|CYv9 zmWQDavn^ub58hw6OD*?i9;}3rdv;T)qy0U1Wr+0R;f<`s5_V-~Wq`sfJuE#Mpn0Q~ zv1JkW+O#4zMxi2}+U>T?M(pmm=Q8gF_+C@b=bLR zsZPEr%I;gHZeb)RdtBuW3baCn`CTH4uYVAen|61>qsxn+Uz!RqFkv{-7_I-FEbMqm zJO)!(Cxm^h=mjicn9yZOL~kbn)$Ekn73P*PiGM7mo{2&eP#nwzYjeh>OUop}eN%D%F0$2n#V zvL;d=`&+U$p7X;%PcuEg$HxkXLDymqp@!@GCEMJI)A3DhD=sZTb@$^DE7E*uMMqGZ4`f>L@c7Mmhe{o8 zM|Hzc0y%BrB~_jP)#^8ic3n#B#!V_A@79Aah2=g;AHT!y(E2;*-%To+-w3rVMzgB< zSY{m9ASMTaD94rxg|BhM1_lUNdePsiLGXO088it5kwyJRX){R&)@-^As0S^7Jo7aE zO}M3Iios#fA5S(vx0IRqB%m2It;qd@mG7~_owy%e=eI5d_%gO%649g0qO8@TjQl3= zc^vG0DEg81<-c>;c&udTS86A{jQdUZjuok^>?_q1hZoYwp2xGP5X6oa=r+dd zHj&jda02)U@Uw75$>wb62#)b>Ld)1DH5bip^ETJt=7qj}_3g((VnWR*PR|YdC+FWc2~T-}|OiV5aU2z%zY1pkaF`H3%Uet^TIyV2Lx!F)=u zm#zm$geFWUrSn8xm9v12u~q8Tf|;cSB)kod9m1^NEuT$B`ghWeO>(-90^RMVn@y;P z$_m{KLO52nciNpMm@V}$4A0(du2$UX*8a#bsIK4PTv;Efo6?iNA(`xl*q0l)0xa1P zr)m|>BiMP)b&UGWFlvf{zX?;CU2v7Rl_?HTQ!AjgB_uwRZGZ5AUsqq?Gu`T~^CZxiDKh){iy*K8 z&4vjtkOfa1QTf}>FJ5FzZYEB$p{%#>RPC&J9;#)4hHwS(0bR~6@-*ZA>Z=_dF|cZYu))-hqcqAT~B|MbAb zojcR&8s}OhP}eRRngFfaL9APMXRU#6eQP4XB@NDQlMm8ZLDt?#%f$jQSAlsv^LO!) zUACVL1LKvCxwb2=_DWs1$+A(f8wMTcgS4b>I10z5xZqqhayiNY?aRDRZ9utxW7+*h zZ|R;J+iwLcHarK;MmO?Fw3d`4wwrR`#T!{Q6hm4;D7AUQ9!Qkq1MJ(UrGIuef9id2HOi z*R|fWwL6>Dg!*?gn$CliGjYOu%Il7|qex#t8Z`QhS@R}8@dyEMNvDB_ZE3rSsgyrP zD#n*ycl#WUE&v_eFJVoJt-t`m%ZH=LvOES(ug5$K=`Kp|h&(F3b}8e%CbHGDcB(&^ zVRP)HvD3B7s86xuC2D^F*1O5GzeIPG|?rFBY zmUzRz+VDstF1K$Yji`zU^IvWGSgn!|w3&B6dZvcfD~k+EYr!QOhL1SRGIf>pcM!Wk zI@5uK`SiOSMW|=3uQqfa3liG6@M+f>v{!tG1oU1IldpX1))YDX zE%M7?V(jwZ(<1UFe2K*t`O;x}eD@q6J6YhO@ajCUsRRGY1)fkYUACRuAO>)NKj^cs zwxo9n-IoW7V2S?Tvp+)iS#PYPF1g0J;5#{FfvRp|GulAtMI{h!_-Fn4_A&Gif#;g2 zJo@VY)!ubRHI;4QE4>B=i~^!`0fT^uAcWq8P(?zMCS_0r6=jStm_Pt2LkR-nfMbFN zX<>*JqzeiRj8s984kBPc2muKYl6NgQ<;{C*)_d=VKi*nd>)vzLx##S?&t3PNll|>) ztG#8noy*VRD{vR9wJMAdZZxYFZpWIs1S1_EKOn26#!3i-i6XV3hrdAQ>I=qCi*`^p zsz-ub)CY3=`Hcg090IFiE@?u2&?Y05uRzjg2UR*}I}S{*LN;j401-(mz*b8&VyS8W3={f__H;a9WA)9ci&vx30VO#A{KkaYS}GBQ20mrN7CKd z4*A$H5S91y=P+#{TIg5iNE{}}S!s%>_ShXN*KUC<$`u3Q`(xWIx&!mZ5U|(INOw3f z^VXdSY@w;~IarF06%+?4>OK9cxbIO4ylRrWmOvJEe}ug$W?Qx8XY8I0NDU<@@tdE5 zXqj6?Y=bTdm9JHNMF+~Ls$D_|_rj=+{A6QZ>RrHC`!^7F7EzL#TX zbY{8A5r<@;JL$9#Nv@n)t=8HQi{R&a)sOnl9Ki9)pZuq2C2@^lp1mH}?p!1321htu zLgpk?GR9WWBy|XRlqik*_6sgTt%>W&SaNbOF$40A8@;k6-Z*AX9mm<0V;`G8d+I*l zsjxFD1i9C<(Pq11f0ZTP!+|6}@`{}lld3*lcPnO4DVJoapl<*}uDSuPXI+{|tqW3* zJb=o4RNO2QbreF(ggCOB^;%?`D-c!ME;(E?^ST%P&UeE$A;$#hEG(6-6&sP&4VugJ zK_tO`5_COq?3k18m-x#aa)f1M3bL=Qk=sh_ow=DSsxkyyh}b9nT)NWo%1Mc;XAfj$ zD`26W6J&3Xmkwq~1#{QQ<{aldf0g#Vb6P|udA}nzHCpdnuBNxq~Zd5z1_0%MvtZS@!z&}p#xUZAa144KJSu$bi6T1WCGQ2O-C-I zfH?T_jC^YzA$wpP;+vH?Y+QC2NWa^Z_qfX>=K27qsEE*DaD|`>PiAOi65Y~Bx;V>R z9;9`K9tK_bg&>%$>7HkxK@Agnl`>P@Th{I*GTTaUmNa;1RH>8IwN#;u8X{%8v4HIT zo8}S+zy+8x>)c0s3xZaAKY(HP4OfkVXjm%tng(U2qz`nnJaelJ_&yvy38Gv<6l@Ci zayG{NBi;Z>`2bFsbyKbJ#X(KuraN1AjFI3zixeV?DXDB5d%{AvD*?^|(7#c2d}i$U za&e?@tcKoS#CTJ}K__8HTPiyDhsZ(7P7KFWxRx&q=m%M>pMGimk?rH}l{QjO?%W<* z75TRL{VAS4lzHahzaAE9V+J$W&BVdM3`lrJ-bxs1e##KlsV zO@VVJt?IYx+Un4o8$Y};FdQ=2+*n;0{ZRbfW#XW>;`1Np+z?ha{$l<;0sRV(`3Lf! z8y^x4@*F>a8$5(cIY&zerwd2mNfbh5jJaIk?7teFgR0wTAP# z0vEjRbRQE0Tsig9AFn099HjcLi!@F(PO&~hsS4}x{XQAcZ2-!-72Ym{>?o3jTTs&hzT9ZLZKb!RrgKtx&P9sHXDjw#NRj^gA8L3E%O`|Jp+_{n4QG=iN(!z#Eq?fZZ57L- z6mh@`w#fokp@{kKL7LtLExzRZp3Slakn2Z*zH;U9|6usPRDij#fkCS?MkcitY%+-& z+tZQU7KeGdbc&_#6@MoNeYDdh%g^D3y*SK)fS9Y1l~ukxk@NE{uB0m6K#e-SM!W7) zb4hSoTs4Ob!`e2>(#T?tsur8wK*NMg9;`P;#znd)mnjMRd3_uFwba9+=Vk-;FkXY7 zi;cjzH_X8nfr-)wMN@cEuQFduB~6b7tRxMhY@f|kkLuLj;B=5sit&Lw3F!Bn@>JL@ z;G26hH0&n4^P`Rvd5afIlm%U+%!}Dld*6UvRu7expm#Pb)rCx62kc6eab>krdHNy= zFNQ$Y>u@gX!WUn)asI=}vPnb6DIfXi;3AsLz^`D=E}*NDg9|m6GKEcjf)82{>YaRC zRr;j`#DqXftHg>PwHxO#nu0PNGZT&e@VA9^~ z2ECO9@Kz#Tu7;D>P|kPPHopaPs)rWbvWpDB#11Q*-l*=6tSWJ3Fz;hx4)=5^8fert zoVogNb0pZnvzLU%xYa$macU+5FYZ0AZRO5f(2`Pp+h=I_f6@Qi>Mb{hBN`;>LgTRB zH5QOE%?uxci50JS8KzF5G4#DoCGF#%K&n>A2XfiUQO#JubG%s$)U4kZtH$lbUdQ0-&C0K3O>!9v#ok9%jJdlwiYnZAc zLbT|k)fG|V4*pW`=i%EEhgH)a9kNI$Uzk>v=5i9}ZeOyrEIiuZ3|!0>Cc!4Ao&v2g z(rY7Dyih*4*chaH0vZ?T@u%l41Aei+GrDUvO|h%~NI|<-K2XxeUMY!dyP9iTtG%Ed zm~EW1q%2zLDHZ(h>OKoV^D>u3S>K*dIeKgV##(d6%$Quyv>}5^LuU)K?-Pib4wsjA zQkwINf?FpzL#gK{fB<7$qKO-m8n2xLW?!c z>o7Xtg01Elg`A2WhI~@`uVmX}e>*D8{zeUAo69xsFrBRqJ&XhjHH3b8Q=Me?F8j#f zrx!5yej8!Sj9X^d60=M;9iHFyzNxTl$0lRTkf&RScC%rZ?`=&5xTxWom)0JmSb+Bg zJgYUUlIzl7ur(QykRboW^%Zd>+qHvyPyaS8K@FmAA7ARb*QBIe{QZt25743Tlc8jp zU)jRUaNUyz4K44}+7&|}S{yo2b_rJ{JOcaO_H?a1-5D>Y*w{yQ)+*A@?~`-qpxe|? zX|48#{Mfq>t!h-sesW1;9P;8|BX->bOj0jKP z4{h5oHV6 z$@9lfztMntRvaj>`NJ4~pOlxbKK#UB&%=Zt`F0iIs|u)qJCrFT(h@n{4G8$YLdqbR=3s3-l50Q2(4 z9XP3=4EzHA`P}gL`YkHEJE(k2Ix1zfTO!ADO&hoDuP-iSVcPYz-K0^rEn2*3e"] +description = "a simple tui to view & control docker containers" +repository = "https://github.com/mrjackwills/oxker" +license = "MIT" +readme = "README.md" + +[dependencies] +anyhow = "1.0" +bollard = "0.12.0" +cansi = "2.1.1" +clap={version="3.1.0", features = ["derive", "unicode"] } +crossterm = "0.23.2" +futures-util = "0.3.21" +parking_lot = {version= "0.12.0"} +tokio = {version = "1.17.0", features=["full"]} +tracing = "0.1.32" +tracing-subscriber = "0.3.9" +tui = "0.17" + +[dev-dependencies] + +[profile.release] +lto = true +codegen-units = 1 +panic = 'abort' +strip=true +debug = false + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..461de66 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Jack Wills + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e705f95 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +

+ +

+ +

+

oxker

+

+ +

+ A simple tui to view and control docker containers" +

+ +

+ Built in Rust, making heavy use of tui-rs & Bollard +

+ +

+ + + +

+ + +## Download + +See releases + + +## Run + +```./oxker``` + +available command line arguments +| argument|result| +|--|--| +|```-d [number > 0]```| set the update interval for docker information, is ms | +|```-r```| Show raw logs, by default oxker will remove ANSI formatting (conflicts with -c) | +|```-c```| Attempt to color the logs (conflicts with -r) | +|```-t```| Remove timestamps from each log entry | +|```-g```| No tui, basically a pointless debugging mode, for now | + +## Build step + +### x86_64 + +```cargo build --release``` + +### Raspberry pi + +requires docker & cross-rs + + +#### 64bit pi (pi 4, pi zero w 2) + +```cross build --target aarch64-unknown-linux-gnu --release``` + +#### 32bit pi (pi zero w) + +Tested, and fully working on pi zero w, running Raspberry Pi OS 32 bit, the initial logs parsing can take an extended period of time if thousands of lines long + +```cross build --target arm-unknown-linux-musleabihf --release``` + +If no memory information available, try appending ```/boot/cmdline.txt``` with + +```cgroup_enable=cpuset cgroup_enable=memory``` + +see https://forums.raspberrypi.com/viewtopic.php?t=203128 and https://github.com/docker/for-linux/issues/1112 + +### Compress executable + +compress output from \~3mb to ~1mb + +```upx --best --lzma target/release/oxker -o ./oxker``` + +### Untested on other platforms + +## Tests + +As of yet untested, needs work + +```cargo test -- --test-threads=1``` + +Run some example docker images + +```docker run --name redis -d redis:alpine3.15``` + +```docker run --name postgres -e POSTGRES_PASSWORD=never_use_this_password_in_production -d postgres:alpine``` + +```docker run -d --hostname my-rabbit --name rabbitmq rabbitmq:3``` + + diff --git a/create_release.sh b/create_release.sh new file mode 100755 index 0000000..594fcd7 --- /dev/null +++ b/create_release.sh @@ -0,0 +1,248 @@ +#!/bin/bash + +# rust create_release +# v0.0.14 + +PACKAGE_NAME='oxker' +STAR_LINE='****************************************' +CWD=$(pwd) + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +PURPLE='\033[0;35m' +RESET='\033[0m' + + +# $1 string - error message +error_close() { + echo -e "\n${RED}ERROR - EXITED: ${YELLOW}$1${RESET}\n"; + exit 1 +} + + +if [ -z "$PACKAGE_NAME" ] +then + error_close "No package name" +fi + +# $1 string - question to ask +ask_yn () { + printf "%b%s? [y/N]:%b " "${GREEN}" "$1" "${RESET}" +} + +# return user input +user_input() { + read -r data + echo "$data" +} + +update_major () { + local bumped_major + bumped_major=$((MAJOR + 1)) + echo "${bumped_major}.0.0" +} + +update_minor () { + local bumped_minor + bumped_minor=$((MINOR + 1)) + echo "${MAJOR}.${bumped_minor}.0" +} + +update_patch () { + local bumped_patch + bumped_patch=$((PATCH + 1)) + echo "${MAJOR}.${MINOR}.${bumped_patch}" +} + +# Get the url of the github repo, strip .git from the end of it +get_git_remote_url() { + REMOTE_ORIGIN=$(git config --get remote.origin.url) + TO_REMOVE=".git" + GIT_REPO_URL="${REMOTE_ORIGIN//$TO_REMOVE}" +} + +# Check that git status is clean +check_git_clean() { + GIT_CLEAN=$(git status --porcelain) + if [[ -n $GIT_CLEAN ]] + then + error_close "git dirty" + fi +} + +# Check currently on dev branch +check_git() { + CURRENT_GIT_BRANCH=$(git branch --show-current) + check_git_clean + if [[ ! "$CURRENT_GIT_BRANCH" =~ ^dev$ ]] + then + error_close "not on dev branch" + fi +} + +# Ask user if current changelog is acceptable +ask_changelog_update() { + echo "${STAR_LINE}" + RELEASE_BODY_TEXT=$(sed '/# CHANGELOG.md for more details" > .github/release-body.md + echo -e "# ${NEW_TAG_VERSION}\n${DATE_SUBHEADING}${CHANGELOG_ADDITION}$(cat CHANGELOG.md)" > CHANGELOG.md + sed -i -E "s=(\s)\[([0-9a-f]{40})\](\n|\s|\,|\r)= [\2](${GIT_REPO_URL}/commit/\2),=g" ./CHANGELOG.md +} + +# update version in cargo.toml, to match selected current version/tag +update_cargo_toml () { + sed -i "s|^version = .*|version = \"${NEW_TAG_VERSION:1}\"|" Cargo.toml +} + +# Work out the current version, based on git tags +# create new semver version based on user input +check_tag () { + LATEST_TAG=$(git describe --tags --abbrev=0 --always) + echo -e "\nCurrent tag: ${PURPLE}${LATEST_TAG}${RESET}\n" + echo -e "${YELLOW}Choose new tag version:${RESET}\n" + if [[ $LATEST_TAG =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]] + then + IFS="." read -r MAJOR MINOR PATCH <<< "${LATEST_TAG:1}" + else + MAJOR="0" + MINOR="0" + PATCH="0" + fi + MAJOR_TAG=v$(update_major) + MINOR_TAG=v$(update_minor) + PATCH_TAG=v$(update_patch) + OP_MAJOR="major___$MAJOR_TAG" + OP_MINOR="minor___$MINOR_TAG" + OP_PATCH="patch___$PATCH_TAG" + OPTIONS=("$OP_MAJOR" "$OP_MINOR" "$OP_PATCH") + select choice in "${OPTIONS[@]}" + do + case $choice in + "$OP_MAJOR" ) + NEW_TAG_VERSION="$MAJOR_TAG" + break;; + "$OP_MINOR") + NEW_TAG_VERSION="$MINOR_TAG" + break;; + "$OP_PATCH") + NEW_TAG_VERSION="$PATCH_TAG" + break;; + *) + error_close "invalid option $REPLY" + break;; + esac + done +} + +# ask continue, or quit +ask_continue () { + ask_yn "continue" + if [[ ! "$(user_input)" =~ ^y$ ]] + then + exit + fi +} + +# run all tests +cargo_test () { + cargo test -- --test-threads=1 + ask_continue +} + +# Build for linux, pi 32, pi 64, and windows +cargo_build_all() { + cargo build --release + cross build --target aarch64-unknown-linux-musl --release + cross build --target arm-unknown-linux-musleabihf --release + cross build --target x86_64-pc-windows-gnu --release + tar -C target/arm-unknown-linux-musleabihf/release -czf ./releases/oxker_linux_armv6.tar.gz oxker + tar -C target/aarch64-unknown-linux-musl/release -czf ./releases/oxker_linux_aarch64.tar.gz oxker + zip -j ./releases/oxker_windows_x86_64.zip target/x86_64-pc-windows-gnu/release/oxker.exe + tar -C target/release -czf ./releases/oxker_linux_x86_64.tar.gz oxker +} + +# Full flow to create a new release +release_flow() { + check_git + get_git_remote_url + cargo_test + cd "${CWD}" || error_close "Can't find ${CWD}" + check_tag + printf "\nnew tag chosen: %s\n\n" "${NEW_TAG_VERSION}" + RELEASE_BRANCH=release-$NEW_TAG_VERSION + echo -e + ask_changelog_update + git checkout -b "$RELEASE_BRANCH" + update_cargo_toml + git add . + git commit -m "chore: release $NEW_TAG_VERSION" + git checkout main + git merge --no-ff "$RELEASE_BRANCH" -m "chore: merge ${RELEASE_BRANCH} into main" + git tag -am "${RELEASE_BRANCH}" "$NEW_TAG_VERSION" + echo "git tag -am \"${RELEASE_BRANCH}\" \"$NEW_TAG_VERSION\"" + git push --atomic origin main "$NEW_TAG_VERSION" + git checkout dev + git merge --no-ff main -m 'chore: merge main into dev' + git branch -d "$RELEASE_BRANCH" +} + + +main() { + cmd=(dialog --backtitle "Choose build option" --radiolist "choose" 14 80 16) + options=( + 1 "fmt" off + 2 "build" off + 3 "test" off + 4 "release" off + ) + choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) + exitStatus=$? + clear + if [ $exitStatus -ne 0 ]; then + exit + fi + for choice in $choices + do + case $choice in + 0) + exit + break;; + 1) + cargo fmt + main + break;; + 2) + cargo_build_all + main + break;; + 3) + npm_test + main + break;; + 4) + release_flow + break;; + esac + done +} + +main \ No newline at end of file diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs new file mode 100644 index 0000000..084fbcb --- /dev/null +++ b/src/app_data/container_state.rs @@ -0,0 +1,425 @@ +use std::{cmp::Ordering, collections::VecDeque, fmt}; + +use tui::{ + style::Color, + widgets::{ListItem, ListState}, +}; + +#[derive(Debug, Clone)] +pub struct StatefulList { + pub state: ListState, + pub items: Vec, +} + +impl StatefulList { + pub fn new(items: Vec) -> Self { + Self { + state: ListState::default(), + items, + } + } + pub fn end(&mut self) { + let len = self.items.len(); + if len > 0 { + self.state.select(Some(self.items.len() - 1)); + } + } + + pub fn start(&mut self) { + self.state.select(Some(0)); + } + + pub fn next(&mut self) { + if !self.items.is_empty() { + let i = match self.state.selected() { + Some(i) => { + if i < self.items.len() - 1 { + i + 1 + } else { + i + } + } + None => 0, + }; + self.state.select(Some(i)); + } + } + + pub fn previous(&mut self) { + if !self.items.is_empty() { + let i = match self.state.selected() { + Some(i) => { + if i == 0 { + 0 + } else { + i - 1 + } + } + None => 0, + }; + self.state.select(Some(i)); + } + } + + pub fn get_state_title(&self) -> String { + if self.items.is_empty() { + String::from("") + } else { + let len = self.items.len(); + let c = if let Some(value) = self.state.selected() { + if len > 0 { + value + 1 + } else { + value + } + } else { + 0 + }; + format!("{}/{}", c, self.items.len()) + } + } +} + +/// States of the container +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub enum State { + Dead, + Exited, + Paused, + Removing, + Restarting, + Running, + Unknown, +} + +impl State { + pub fn get_color(&self) -> Color { + match self { + Self::Running => Color::Green, + Self::Removing => Color::LightRed, + Self::Restarting => Color::LightGreen, + Self::Paused => Color::Yellow, + _ => Color::Red, + } + } +} + +impl From<&str> for State { + fn from(input: &str) -> Self { + match input { + "dead" => Self::Dead, + "exited" => Self::Exited, + "paused" => Self::Paused, + "removing" => Self::Removing, + "restarting" => Self::Restarting, + "running" => Self::Running, + _ => Self::Unknown, + } + } +} + +impl fmt::Display for State { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let disp = match self { + Self::Dead => "✖ dead", + Self::Exited => "✖ exited", + Self::Paused => "॥ paused", + Self::Removing => "removing", + Self::Restarting => "↻ restarting", + Self::Running => "✓ running", + Self::Unknown => "? unknown", + }; + write!(f, "{}", disp) + } +} + +/// Items for the container control list +/// Should probably have a vec for each container +/// so that can remove Pause if container currently Paused etc +#[derive(Debug, Clone)] +pub enum DockerControls { + Pause, + Unpause, + Restart, + Stop, + Start, +} + +impl DockerControls { + pub fn get_color(&self) -> Color { + match self { + Self::Start => Color::Green, + Self::Stop => Color::Red, + Self::Restart => Color::Magenta, + Self::Pause => Color::Yellow, + Self::Unpause => Color::Blue, + } + } + + pub fn gen_vec(state: &State) -> Vec { + match state { + State::Dead | State::Exited => vec![Self::Start, Self::Restart], + State::Paused => vec![Self::Unpause, Self::Stop], + State::Running => vec![Self::Pause, Self::Restart, Self::Stop], + _ => vec![], + } + } +} + +impl fmt::Display for DockerControls { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let disp = match self { + Self::Pause => "pause", + Self::Unpause => "unpause", + Self::Restart => "restart", + Self::Stop => "stop", + Self::Start => "start", + }; + write!(f, "{}", disp) + } +} + +pub trait Stats { + fn get_value(&self) -> f64; +} + +/// Struct for frequently updated CPU stats +/// So can use custom display formatter +/// Use trait Stats for use as generic in draw_chart function +#[derive(Clone, Debug)] +pub struct CpuStats { + value: f64, +} + +impl CpuStats { + pub fn new(value: f64) -> Self { + Self { value } + } +} + +impl Eq for CpuStats {} + +impl PartialEq for CpuStats { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl PartialOrd for CpuStats { + fn partial_cmp(&self, other: &Self) -> Option { + self.value.partial_cmp(&other.value) + } +} + +impl Ord for CpuStats { + fn cmp(&self, other: &Self) -> Ordering { + if self.value > other.value { + Ordering::Greater + } else if self.value == other.value { + Ordering::Equal + } else { + Ordering::Less + } + } +} + +impl Stats for CpuStats { + fn get_value(&self) -> f64 { + self.value + } +} + +impl fmt::Display for CpuStats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let disp = format!("{:05.2}%", self.value); + write!(f, "{:>x$}", disp, x = f.width().unwrap_or(1)) + } +} + +/// Struct for frequently updated memory usage stats +/// So can use custom display formatter +/// Use trait Stats for use as generic in draw_chart function +#[derive(Clone, Debug, Eq)] +pub struct ByteStats { + value: u64, +} + +impl PartialEq for ByteStats { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl PartialOrd for ByteStats { + fn partial_cmp(&self, other: &Self) -> Option { + self.value.partial_cmp(&other.value) + } +} + +impl Ord for ByteStats { + fn cmp(&self, other: &Self) -> Ordering { + self.value.cmp(&other.value) + } +} + +impl ByteStats { + pub fn new(value: u64) -> Self { + Self { value } + } + pub fn update(&mut self, value: u64) { + self.value = value; + } +} +impl Stats for ByteStats { + fn get_value(&self) -> f64 { + self.value as f64 + } +} + +// convert from bytes to kb, mb, gb etc +impl fmt::Display for ByteStats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let one_kb = 1000.0; + let one_mb = one_kb * one_kb; + let one_gb = one_mb * 1000.0; + let as_f64 = self.value as f64; + let p = match as_f64 { + x if x >= one_gb => format!("{y:.2} GB", y = as_f64 / one_gb), + x if x >= one_kb => format!("{y:.2} MB", y = as_f64 / one_mb), + x if x >= one_mb => format!("{y:.2} kB", y = as_f64 / one_kb), + _ => format!("{} B", self.value), + }; + write!(f, "{:>x$}", p, x = f.width().unwrap_or(1)) + } +} + +/// Info for each container +#[derive(Debug, Clone)] +pub struct ContainerItem { + pub cpu_stats: VecDeque, + pub docker_controls: StatefulList, + pub id: String, + pub image: String, + pub last_updated: u64, + pub logs: StatefulList>, + pub mem_limit: ByteStats, + pub mem_stats: VecDeque, + pub name: String, + pub net_rx: ByteStats, + pub net_tx: ByteStats, + pub state: State, + pub status: String, +} + +pub type MemTuple = (Vec<(f64, f64)>, ByteStats, State); +pub type CpuTuple = (Vec<(f64, f64)>, CpuStats, State); + +impl ContainerItem { + /// Create a new container item + pub fn new(id: String, status: String, image: String, state: State, name: String) -> Self { + let mut docker_controls = StatefulList::new(DockerControls::gen_vec(&state)); + docker_controls.start(); + Self { + cpu_stats: VecDeque::with_capacity(60), + docker_controls, + id, + image, + last_updated: 0, + logs: StatefulList::new(vec![]), + mem_limit: ByteStats::new(0), + mem_stats: VecDeque::with_capacity(60), + name, + net_rx: ByteStats::new(0), + net_tx: ByteStats::new(0), + state, + status, + } + } + + /// Find the max value in the last 30 items in the cpu stats vec + fn max_cpu_stats(&self) -> CpuStats { + match self.cpu_stats.iter().max() { + Some(value) => value.to_owned(), + None => CpuStats::new(0.0), + } + } + + /// Find the max value in the last 30 items in the mem stats vec + fn max_mem_stats(&self) -> ByteStats { + match self.mem_stats.iter().max() { + Some(value) => value.to_owned(), + None => ByteStats::new(0), + } + } + + /// Convert cpu stats into a vec for the charts function + fn get_cpu_dataset(&self) -> Vec<(f64, f64)> { + self.cpu_stats + .iter() + .enumerate() + .map(|i| (i.0 as f64, i.1.value)) + .collect::>() + } + + /// Convert mem stats into a vec for the charts function + fn get_mem_dataset(&self) -> Vec<(f64, f64)> { + self.mem_stats + .iter() + .enumerate() + .map(|i| (i.0 as f64, i.1.value as f64)) + .collect::>() + } + + /// Get all cpu chart data + fn get_cpu_chart_data(&self) -> CpuTuple { + ( + self.get_cpu_dataset(), + self.max_cpu_stats(), + self.state.clone(), + ) + } + + /// Get all mem chart data + fn get_mem_chart_data(&self) -> MemTuple { + ( + self.get_mem_dataset(), + self.max_mem_stats(), + self.state.clone(), + ) + } + + /// Get chart info for cpu & memory in one function + /// So only need to call .lock() once + pub fn get_chart_data(&self) -> (CpuTuple, MemTuple) { + (self.get_cpu_chart_data(), self.get_mem_chart_data()) + } +} + +/// Container information panel headings + widths, for nice pretty formatting +#[derive(Debug)] +pub struct Columns { + pub cpu: (String, usize), + pub image: (String, usize), + pub name: (String, usize), + pub state: (String, usize), + pub status: (String, usize), + pub mem: (String, usize), + pub net_rx: (String, usize), + pub net_tx: (String, usize), +} + +impl Columns { + pub fn new() -> Self { + Self { + // 7 to allow for 100.00% + cpu: (String::from("cpu"), 7), + image: (String::from("image"), 5), + name: (String::from("name"), 4), + state: (String::from("state"), 11), + status: (String::from("status"), 16), + mem: (String::from("mem/limit"), 9), + net_rx: (String::from("↓ rx"), 5), + net_tx: (String::from("↑ tx"), 5), + } + } +} diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs new file mode 100644 index 0000000..f0094b1 --- /dev/null +++ b/src/app_data/mod.rs @@ -0,0 +1,397 @@ +use bollard::models::ContainerSummary; +use std::time::{SystemTime, UNIX_EPOCH}; +use tui::widgets::ListItem; + +mod container_state; + +use crate::{app_error::AppError, parse_args::CliArgs, ui::log_sanitizer}; +pub use container_state::*; + +/// Global app_state, stored in an Arc +#[derive(Debug)] +pub struct AppData { + args: CliArgs, + error: Option, + logs_parsed: bool, + pub containers: StatefulList, + pub init: bool, + pub show_error: bool, +} + +impl AppData { + /// Generate a default app_state + pub fn default(args: CliArgs) -> Self { + Self { + args, + containers: StatefulList::new(vec![]), + error: None, + init: false, + logs_parsed: false, + show_error: false, + } + } + + + // Current time as unix timestamp + fn get_systemtime(&self) -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("In our known reality, this error should never occur") + .as_secs() + } + + + /// Get the current select docker command + /// So know which command to execute + pub fn get_docker_command(&self) -> Option { + let mut output = None; + if let Some(index) = self.containers.state.selected() { + if let Some(control_index) = self.containers.items[index] + .docker_controls + .state + .selected() + { + output = + Some(self.containers.items[index].docker_controls.items[control_index].clone()) + } + } + output + } + + /// Change selected choice of docker commands of selected container + pub fn docker_command_next(&mut self) { + if let Some(index) = self.containers.state.selected() { + self.containers.items[index].docker_controls.next() + } + } + + /// Change selected choice of docker commands of selected container + pub fn docker_command_previous(&mut self) { + if let Some(index) = self.containers.state.selected() { + self.containers.items[index].docker_controls.previous() + } + } + + /// Change selected choice of docker commands of selected container + pub fn docker_command_start(&mut self) { + if let Some(index) = self.containers.state.selected() { + self.containers.items[index].docker_controls.start() + } + } + + /// Change selected choice of docker commands of selected container + pub fn docker_command_end(&mut self) { + if let Some(index) = self.containers.state.selected() { + self.containers.items[index].docker_controls.end() + } + } + + /// return single app_state error + pub fn get_error(&self) -> Option { + self.error.clone() + } + + /// remove single app_state error + pub fn remove_error(&mut self) { + self.error = None; + } + + /// insert single app_state error + pub fn set_error(&mut self, error: AppError) { + self.error = Some(error); + } + + /// Find the if of the currently selected container + /// If any containers on system, will always return + /// Only returns None when no containers found + pub fn get_selected_container_id(&self) -> Option { + let mut output = None; + if let Some(index) = self.containers.state.selected() { + let id = self + .containers + .items + .iter() + .skip(index) + .take(1) + .map(|i| i.id.to_owned()) + .collect::(); + output = Some(id) + } + output + } + + /// Find the index of the currently selected single log line + pub fn get_selected_log_index(&self) -> Option { + let mut output = None; + if let Some(id) = self.get_selected_container_id() { + if let Some(index) = self.containers.items.iter().position(|i| i.id == id) { + output = Some(index); + } + } + output + } + + /// Get the title for log panel for selected container + /// will be "logs x/x" + pub fn get_log_title(&self) -> String { + if let Some(index) = self.get_selected_log_index() { + self.containers.items[index].logs.get_state_title() + } else { + String::from("") + } + } + + /// select next selected log line + pub fn log_next(&mut self) { + if let Some(index) = self.get_selected_log_index() { + self.containers.items[index].logs.next() + } + } + + /// select previous selected log line + pub fn log_previous(&mut self) { + if let Some(index) = self.get_selected_log_index() { + self.containers.items[index].logs.previous() + } + } + + /// select last selected log line + pub fn log_end(&mut self) { + if let Some(index) = self.get_selected_log_index() { + self.containers.items[index].logs.end() + } + } + + /// select first selected log line + pub fn log_start(&mut self) { + if let Some(index) = self.get_selected_log_index() { + self.containers.items[index].logs.start() + } + } + + pub fn initialised(&mut self, all_ids: &[(bool, String)]) -> bool { + let count_is_running = all_ids.iter().filter(|i| i.0).count(); + let number_with_cpu_status = self + .containers + .items + .iter() + .filter(|i| !i.cpu_stats.is_empty()) + .count(); + self.logs_parsed && count_is_running == number_with_cpu_status + } + + /// Just get the total number of containers + pub fn get_container_len(&self) -> usize { + self.containers.items.len() + } + + /// Find the widths for the strings in the containers panel + /// So can display nicely and evenly + pub fn get_width(&self) -> Columns { + let mut output = Columns::new(); + let count = |x: &String| x.chars().count(); + + for container in self.containers.items.iter() { + let cpu_count = count( + &container + .cpu_stats + .back() + .unwrap_or(&CpuStats::new(0.0)) + .to_string(), + ); + let mem_count = count(&format!( + "{} / {}", + container.mem_stats.back().unwrap_or(&ByteStats::new(0)), + container.mem_limit + )); + + let net_rx_count = count(&container.net_rx.to_string()); + let net_tx_count = count(&container.net_tx.to_string()); + let image_count = count(&container.image); + let name_count = count(&container.name); + let state_count = count(&container.state.to_string()); + let status_count = count(&container.status); + + if cpu_count > output.cpu.1 { + output.cpu.1 = cpu_count; + }; + if image_count > output.image.1 { + output.image.1 = image_count; + }; + if mem_count > output.mem.1 { + output.mem.1 = mem_count; + }; + if name_count > output.name.1 { + output.name.1 = name_count; + }; + if state_count > output.state.1 { + output.state.1 = state_count; + }; + if status_count > output.status.1 { + output.status.1 = status_count; + }; + + if net_rx_count > output.net_rx.1 { + output.net_rx.1 = net_rx_count; + }; + + if net_tx_count > output.net_tx.1 { + output.net_tx.1 = net_tx_count; + }; + } + output + } + + /// Get all containers ids + pub fn get_all_ids(&self) -> Vec { + self.containers + .items + .iter() + .map(|i| i.id.to_owned()) + .collect::>() + } + + /// find container given id + fn get_container_by_id(&mut self, id: &str) -> Option<&mut ContainerItem> { + self.containers.items.iter_mut().find(|i| i.id == id) + } + + /// Update container mem + cpu stats, in single function so only need to call .lock() once + pub fn update_stats( + &mut self, + id: String, + cpu_stat: Option, + mem_stat: Option, + mem_limit: u64, + rx: u64, + tx: u64, + ) { + if let Some(container) = self.get_container_by_id(&id) { + if container.cpu_stats.len() >= 60 { + container.cpu_stats.pop_front(); + } + if container.mem_stats.len() >= 60 { + container.mem_stats.pop_front(); + } + + if let Some(cpu) = cpu_stat { + container.cpu_stats.push_back(CpuStats::new(cpu)); + } + if let Some(mem) = mem_stat { + container.mem_stats.push_back(ByteStats::new(mem)); + } + + container.net_rx.update(rx); + container.net_tx.update(tx); + container.mem_limit.update(mem_limit); + } + } + + /// Update, or insert, containers + pub fn update_containers(&mut self, containers: &[ContainerSummary]) { + let all_ids = self.get_all_ids(); + + if !containers.is_empty() && self.containers.state.selected().is_none() { + self.containers.start(); + } + + for (index, id) in all_ids.iter().enumerate() { + if !containers + .iter() + .map(|i| i.id.as_ref().unwrap()) + .any(|x| x == id) + { + // If removed container is currently selected, then change selected to previous + // This will default to 0 in any edge cases + if self.containers.state.selected().is_some() { + self.containers.previous(); + } + self.containers.items.remove(index); + } + } + + for i in containers.iter() { + let id = i.id.as_ref().unwrap().to_owned(); + let mut name = i + .names + .as_ref() + .unwrap_or(&vec!["".to_owned()]) + .get(0) + .unwrap() + .to_owned(); + if let Some(c) = name.chars().next() { + if c == '/' { + name.remove(0); + } + } + + let state = State::from(i.state.as_ref().unwrap_or(&"dead".to_owned()).trim()); + let status = i + .status + .as_ref() + .unwrap_or(&"".to_owned()) + .trim() + .to_owned(); + let image = i.image.as_ref().unwrap_or(&"".to_owned()).trim().to_owned(); + if let Some(current_container) = self.get_container_by_id(&id) { + if current_container.name != name { + current_container.name = name + }; + if current_container.status != status { + current_container.status = status + }; + if current_container.state != state { + current_container.docker_controls.items = DockerControls::gen_vec(&state); + + // Update the list state, needs to be None if the gen_vec returns an empty vec + match state { + State::Removing | State::Restarting | State::Unknown => { + current_container.docker_controls.state.select(None) + } + _ => current_container.docker_controls.start(), + }; + current_container.state = state; + }; + if current_container.image != image { + current_container.image = image + }; + } else { + let mut container = ContainerItem::new(id, status, image, state, name); + container.logs.end(); + self.containers.items.push(container); + } + } + } + + /// update logs of a given container, based on index not id + pub fn update_log_by_index(&mut self, output: Vec, index: usize) { + let tz = self.get_systemtime(); + if let Some(container) = self.containers.items.get_mut(index) { + container.last_updated = tz; + let current_len = container.logs.items.len(); + output.iter().for_each(|i| { + let lines = if self.args.color { + log_sanitizer::colorize_logs(i.to_owned()) + } else if self.args.raw { + log_sanitizer::raw(i.to_owned()) + } else { + log_sanitizer::remove_ansi(i.to_owned()) + }; + container.logs.items.push(ListItem::new(lines)); + }); + if container.logs.state.selected().is_none() + || container.logs.state.selected().unwrap() + 1 == current_len + { + container.logs.end(); + } + } + self.logs_parsed = true; + } + + + pub fn update_all_logs(&mut self, all_logs: Vec>) { + for (index, output) in all_logs.into_iter().enumerate() { + self.update_log_by_index(output, index); + } + } +} diff --git a/src/app_error.rs b/src/app_error.rs new file mode 100644 index 0000000..ed28d6f --- /dev/null +++ b/src/app_error.rs @@ -0,0 +1,45 @@ +use core::fmt; +use tracing::error; + +use crate::app_data::DockerControls; + +/// app errors to set in global state +#[allow(unused)] +#[derive(Debug, Clone)] +pub enum AppError { + DockerConnect, + DockerInterval, + InputPoll, + DockerCommand(DockerControls), + Terminal, +} + +impl AppError { + /// for handling errors from terminal + pub fn disp(&self) { + match self { + Self::DockerConnect => error!("Unable to access docker daemon"), + Self::DockerInterval => error!("Docker update interval needs to be greater than 0"), + Self::InputPoll => error!("Unable to poll user input"), + Self::Terminal => error!("Unable to draw to terminal"), + Self::DockerCommand(s) => { + let error = format!("Unable to {} container", s); + error!(%error); + } + } + } +} + +/// Convert errors into strings to display +impl fmt::Display for AppError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let disp = match self { + Self::DockerConnect => "Unable to access docker daemon".to_owned(), + Self::DockerInterval => "Docker update interval needs to be greater than 0".to_owned(), + Self::InputPoll => "Unable to poll user input".to_owned(), + Self::Terminal => "Unable to draw to terminal".to_owned(), + Self::DockerCommand(s) => format!("Unable to {} container", s), + }; + write!(f, "{}", disp) + } +} diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs new file mode 100644 index 0000000..0ab2a94 --- /dev/null +++ b/src/docker_data/mod.rs @@ -0,0 +1,277 @@ +use bollard::{ + container::{ListContainersOptions, LogsOptions, Stats, StatsOptions}, + Docker, +}; +use futures_util::{future::join_all, StreamExt}; +use parking_lot::Mutex; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +use crate::{app_data::AppData, parse_args::CliArgs, ui::GuiState}; + +pub struct DockerData { + app_data: Arc>, + docker: Arc, + gui_state: Arc>, + initialised: bool, + sleep_duration: Duration, + timestamps: bool, +} + +impl DockerData { + /// Use docker stats for work out current cpu usage + fn calculate_usage(stats: &Stats) -> f64 { + let mut cpu_percentage = 0.0; + let previous_cpu = stats.precpu_stats.cpu_usage.total_usage; + let cpu_delta = stats.cpu_stats.cpu_usage.total_usage as f64 - previous_cpu as f64; + if stats.cpu_stats.system_cpu_usage.is_some() + && stats.precpu_stats.system_cpu_usage.is_some() + { + let system_delta = (stats.cpu_stats.system_cpu_usage.unwrap() + - stats.precpu_stats.system_cpu_usage.unwrap()) + as f64; + let online_cpus = stats.cpu_stats.online_cpus.unwrap_or_else(|| { + stats + .cpu_stats + .cpu_usage + .percpu_usage + .clone() + .unwrap_or_default() + .len() as u64 + }) as f64; + if system_delta > 0.0 && cpu_delta > 0.0 { + cpu_percentage = (cpu_delta / system_delta) * online_cpus * 100.0; + } + } + cpu_percentage + } + + /// Get a single docker stat in order to update mem and cpu usage + /// don't take &self, so that can tokio::spawn into it's on thread + async fn update_container_stat( + docker: Arc, + id: String, + app_data: Arc>, + is_running: bool, + ) { + let mut stream = docker + .stats( + &id, + Some(StatsOptions { + stream: false, + one_shot: !is_running, + }), + ) + .take(1); + + while let Some(Ok(stats)) = stream.next().await { + let mem_stat = stats.memory_stats.usage.unwrap_or(0); + let mem_limit = stats.memory_stats.limit.unwrap_or(0); + + let key = if let Some(networks) = &stats.networks { + networks.keys().next().map(|x| x.to_owned()) + } else { + None + }; + + let cpu_stats = Self::calculate_usage(&stats); + + let (rx, tx) = if let Some(k) = key { + let ii = stats.networks.unwrap(); + let v = ii.get(&k).unwrap(); + (v.rx_bytes.to_owned(), v.tx_bytes.to_owned()) + } else { + (0, 0) + }; + + if is_running { + app_data.lock().update_stats( + id.clone(), + Some(cpu_stats), + Some(mem_stat), + mem_limit, + rx, + tx, + ); + } else { + app_data + .lock() + .update_stats(id.clone(), None, None, mem_limit, rx, tx); + } + } + } + + /// Update all stats, spawn each container into own tokio::spawn thread + async fn update_all_container_stats(&mut self, all_ids: &[(bool, String)]) { + for (is_running, id) in all_ids.iter() { + let docker = Arc::clone(&self.docker); + let app_data = Arc::clone(&self.app_data); + let is_running = *is_running; + let id = id.to_owned(); + tokio::spawn(async move { + Self::update_container_stat(docker, id, app_data, is_running).await + }); + } + } + + /// Get all current containers, handle into ContainerItem in the app_data struct rather than here + /// Just make sure that items sent are guaranteed to have an id + pub async fn update_all_containers(&mut self) -> Vec<(bool, String)> { + let containers = self + .docker + .list_containers(Some(ListContainersOptions:: { + all: true, + ..Default::default() + })) + .await + .unwrap(); + + let mut output = vec![]; + // iter over containers, to only send ones which have an id, as use ID for extensivley! + // alternative is to create my own container struct, and will out with details + containers.iter().filter(|i| i.id.is_some()).for_each(|c| { + output.push(c.to_owned()); + }); + + self.app_data.lock().update_containers(&output); + output + .iter() + .map(|i| { + ( + i.state.as_ref().unwrap() == "running", + i.id.as_ref().unwrap().to_owned(), + ) + }) + .collect::>() + } + + /// Update single container logs + /// don't take &self, so that can tokio::spawn into it's on thread + async fn update_log( + docker: Arc, + id: String, + timestamps: bool, + since: i64, + ) -> Vec { + let options = Some(LogsOptions:: { + stdout: true, + timestamps, + since, + ..Default::default() + }); + + let mut logs = docker.logs(&id, options); + + let mut output = vec![]; + + while let Some(value) = logs.next().await { + if let Ok(data) = value { + let log_string = data.to_string(); + if !log_string.trim().is_empty() { + output.push(log_string); + } + } + } + output + } + + /// Update all logs, spawn each container into own tokio::spawn thread + // rename init all logs, as only gets run once + async fn update_all_logs(&mut self, all_ids: &[(bool, String)]) { + let mut handles = vec![]; + + for (_, id) in all_ids.iter() { + let docker = Arc::clone(&self.docker); + let timestamps = self.timestamps; + let id = id.to_owned(); + handles.push(Self::update_log(docker, id, timestamps, 0)); + } + let all_logs = join_all(handles).await; + self.app_data.lock().update_all_logs(all_logs); + } + + async fn update_everything(&mut self) { + let all_ids = self.update_all_containers().await; + let op_index = self.app_data.lock().get_selected_log_index(); + if let Some(index) = op_index { + let docker = Arc::clone(&self.docker); + let since = self.app_data.lock().containers.items[index].last_updated as i64; + let timestamps = self.timestamps; + let id = self.app_data.lock().containers.items[index].id.to_owned(); + let logs = Self::update_log(docker, id, timestamps, since).await; + self.app_data.lock().update_log_by_index(logs, index); + }; + + self.update_all_container_stats(&all_ids).await; + } + + /// Initialise self, and start the updated loop + pub async fn init( + args: CliArgs, + app_data: Arc>, + docker: Arc, + gui_state: Arc>, + ) { + if app_data.lock().get_error().is_none() { + let mut inner = Self { + app_data, + docker, + gui_state, + initialised: false, + sleep_duration: Duration::from_millis(args.docker as u64), + timestamps: args.timestamp, + }; + inner.initialise_container_data().await; + inner.update_loop().await; + } + } + + async fn initialise_container_data(&mut self) { + let gui_state = Arc::clone(&self.gui_state); + // could also just loop while init is false, would need to move an arc mutex into here + // so instead just abort at end of function + let loading_spin = tokio::spawn(async move { + loop { + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + gui_state.lock().next_loading(); + } + }); + + let all_ids = self.update_all_containers().await; + self.update_all_container_stats(&all_ids).await; + + // Maybe only do a single one at first? + self.update_all_logs(&all_ids).await; + + if all_ids.is_empty() { + self.initialised = true; + } + + // wait until all logs have initialised + while !self.initialised { + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + self.initialised = self.app_data.lock().initialised(&all_ids); + } + self.app_data.lock().init = true; + loading_spin.abort(); + self.gui_state.lock().reset_loading(); + } + + /// Update all items, wait until all complete + /// sleep for CliArgs.docker ms before updating next + async fn update_loop(&mut self) { + loop { + let start = Instant::now(); + self.update_everything().await; + + let elapsed = start.elapsed(); + if elapsed < self.sleep_duration { + tokio::time::sleep(self.sleep_duration - elapsed).await; + } + } + } +} + +// tests, use redis-test container, check logs exists, and selector of logs, and that it increases, and matches end, when you run restart on the docker containers diff --git a/src/input_handler/message.rs b/src/input_handler/message.rs new file mode 100644 index 0000000..cbefb49 --- /dev/null +++ b/src/input_handler/message.rs @@ -0,0 +1,7 @@ +use crossterm::event::{KeyCode, MouseEvent}; + +#[derive(Debug, Clone)] +pub enum InputMessages { + ButtonPress(KeyCode), + MouseEvent(MouseEvent), +} diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs new file mode 100644 index 0000000..b41940a --- /dev/null +++ b/src/input_handler/mod.rs @@ -0,0 +1,262 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +use bollard::{container::StartContainerOptions, Docker}; +use crossterm::event::{KeyCode, MouseButton, MouseEvent, MouseEventKind}; +use parking_lot::Mutex; +use tokio::sync::broadcast::Receiver; +use tui::layout::Rect; + +mod message; +use crate::{ + app_data::{AppData, DockerControls}, + app_error::AppError, + ui::{GuiState, SelectablePanel}, +}; +pub use message::InputMessages; + +/// Handle all input events +#[derive(Debug)] +pub struct InputHandler { + app_data: Arc>, + docker: Arc, + gui_state: Arc>, + is_running: Arc, + rec: Receiver, +} + +impl InputHandler { + /// Initialize self, and running the message handling loop + pub async fn init( + app_data: Arc>, + rec: Receiver, + docker: Arc, + gui_state: Arc>, + is_running: Arc, + ) { + let mut inner = Self { + app_data, + docker, + gui_state, + is_running, + rec, + }; + inner.start().await; + } + + /// check for incoming messages + async fn start(&mut self) { + while let Ok(message) = self.rec.recv().await { + match message { + InputMessages::ButtonPress(key_code) => self.button_press(key_code).await, + InputMessages::MouseEvent(mouse_event) => { + let show_error = self.app_data.lock().show_error; + let show_info = self.gui_state.lock().show_help; + if !show_error && !show_info { + self.mouse_press(mouse_event); + } + } + } + if !self.is_running.load(Ordering::SeqCst) { + break; + } + } + } + + /// Handle any keyboard button events + async fn button_press(&mut self, key_code: KeyCode) { + let show_error = self.app_data.lock().show_error; + let show_info = self.gui_state.lock().show_help; + if show_error { + match key_code { + KeyCode::Char('q') => { + self.is_running.store(false, Ordering::SeqCst); + } + KeyCode::Char('c') => { + self.app_data.lock().show_error = false; + self.app_data.lock().remove_error(); + } + _ => (), + } + } else if show_info { + match key_code { + KeyCode::Char('q') => { + self.is_running.store(false, Ordering::SeqCst); + } + KeyCode::Char('h') => { + self.gui_state.lock().show_help = false; + } + _ => (), + } + } else { + match key_code { + KeyCode::Char('q') => { + self.is_running.store(false, Ordering::SeqCst); + } + KeyCode::Char('h') => { + self.gui_state.lock().show_help = true; + } + KeyCode::Tab => self.gui_state.lock().next_panel(), + KeyCode::BackTab => self.gui_state.lock().previous_panel(), + KeyCode::Home => { + let mut locked_data = self.app_data.lock(); + match self.gui_state.lock().selected_panel { + SelectablePanel::Containers => locked_data.containers.start(), + SelectablePanel::Logs => locked_data.log_start(), + SelectablePanel::Commands => locked_data.docker_command_start(), + } + } + KeyCode::End => { + let mut locked_data = self.app_data.lock(); + match self.gui_state.lock().selected_panel { + SelectablePanel::Containers => locked_data.containers.end(), + SelectablePanel::Logs => locked_data.log_end(), + SelectablePanel::Commands => locked_data.docker_command_end(), + } + } + KeyCode::Up => self.previous(), + KeyCode::PageUp => { + for _ in 0..=6 { + self.previous() + } + } + KeyCode::Down => self.next(), + KeyCode::PageDown => { + for _ in 0..=6 { + self.next() + } + } + KeyCode::Enter => { + // Does is matter though? + // This isn't great, just means you can't send docker commands before full initialization of the program + // could change to to if loading = true, although at the moment don't have a loading bool + let panel = self.gui_state.lock().selected_panel; + if panel == SelectablePanel::Commands { + let command = self.app_data.lock().get_docker_command(); + + if command.is_some() { + let id = self.app_data.lock().get_selected_container_id(); + let app_data = Arc::clone(&self.app_data); + let docker = Arc::clone(&self.docker); + if id.is_some() { + let id = id.unwrap(); + match command.unwrap() { + DockerControls::Pause => { + tokio::spawn(async move { + docker.pause_container(&id).await.unwrap_or_else( + |_| { + app_data.lock().set_error( + AppError::DockerCommand( + DockerControls::Pause, + ), + ) + }, + ); + }); + } + DockerControls::Unpause => { + tokio::spawn(async move { + docker.unpause_container(&id).await.unwrap_or_else( + |_| { + app_data.lock().set_error( + AppError::DockerCommand( + DockerControls::Unpause, + ), + ) + }, + ); + }); + } + DockerControls::Start => { + tokio::spawn(async move { + docker + .start_container( + &id, + None::>, + ) + .await + .unwrap_or_else(|_| { + app_data.lock().set_error( + AppError::DockerCommand( + DockerControls::Start, + ), + ) + }); + }); + } + DockerControls::Stop => { + tokio::spawn(async move { + docker.stop_container(&id, None).await.unwrap_or_else( + |_| { + app_data.lock().set_error( + AppError::DockerCommand( + DockerControls::Stop, + ), + ) + }, + ); + }); + } + DockerControls::Restart => { + tokio::spawn(async move { + docker + .restart_container(&id, None) + .await + .unwrap_or_else(|_| { + app_data.lock().set_error( + AppError::DockerCommand( + DockerControls::Restart, + ), + ) + }); + }); + } + } + } + } + } + } + _ => (), + } + } + } + + /// Handle mouse button events + fn mouse_press(&mut self, mouse_event: MouseEvent) { + match mouse_event.kind { + MouseEventKind::ScrollUp => self.previous(), + MouseEventKind::ScrollDown => self.next(), + MouseEventKind::Down(MouseButton::Left) => { + self.gui_state.lock().rect_insersects(Rect::new( + mouse_event.column, + mouse_event.row, + 1, + 1, + )); + } + _ => (), + } + } + + /// Change state of selected container + fn next(&mut self) { + let mut locked_data = self.app_data.lock(); + match self.gui_state.lock().selected_panel { + SelectablePanel::Containers => locked_data.containers.next(), + SelectablePanel::Logs => locked_data.log_next(), + SelectablePanel::Commands => locked_data.docker_command_next(), + }; + } + + /// Change state of selected container + fn previous(&mut self) { + let mut locked_data = self.app_data.lock(); + match self.gui_state.lock().selected_panel { + SelectablePanel::Containers => locked_data.containers.previous(), + SelectablePanel::Logs => locked_data.log_previous(), + SelectablePanel::Commands => locked_data.docker_command_previous(), + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..97a3e50 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,76 @@ +use app_data::AppData; +use app_error::AppError; +use bollard::Docker; +use docker_data::DockerData; +use parking_lot::Mutex; +use parse_args::CliArgs; +use std::sync::{atomic::AtomicBool, Arc}; +use tracing::{info, Level}; + +mod app_data; +mod app_error; +mod docker_data; +mod input_handler; +mod parse_args; +mod ui; + +use ui::{create_ui, GuiState}; + +fn setup_tracing() { + tracing_subscriber::fmt().with_max_level(Level::INFO).init(); +} + +#[tokio::main] +async fn main() { + setup_tracing(); + let args = CliArgs::new(); + let app_data = Arc::new(Mutex::new(AppData::default(args.clone()))); + let gui_state = Arc::new(Mutex::new(GuiState::default())); + + let docker_args = args.clone(); + let docker_app_data = Arc::clone(&app_data); + let docker_gui_state = Arc::clone(&gui_state); + + // Create docker daemon handler, and only spawn up the docker data handler if ping returns non-error + let docker = Arc::new(Docker::connect_with_socket_defaults().unwrap()); + match docker.ping().await { + Ok(_) => { + let docker = Arc::clone(&docker); + tokio::spawn(async move { + DockerData::init(docker_args, docker_app_data, docker, docker_gui_state).await; + }); + } + Err(_) => app_data.lock().set_error(AppError::DockerConnect), + } + + let input_app_data = Arc::clone(&app_data); + + let (s, r) = tokio::sync::broadcast::channel(16); + + let input_docker = Arc::clone(&docker); + let is_running = Arc::new(AtomicBool::new(true)); + let input_is_running = Arc::clone(&is_running); + let input_gui_state = Arc::clone(&gui_state); + + // Spawn input handling into own tokio thread + tokio::spawn(async { + input_handler::InputHandler::init( + input_app_data, + r, + input_docker, + input_gui_state, + input_is_running, + ) + .await; + }); + + // Debug mode for testing, mostly pointless, doesn't take terminal nor draw gui + if !args.gui { + loop { + info!("in debug mode"); + tokio::time::sleep(std::time::Duration::from_millis(5000)).await; + } + } else { + create_ui(app_data, s, is_running, gui_state).await.unwrap(); + } +} diff --git a/src/parse_args/mod.rs b/src/parse_args/mod.rs new file mode 100644 index 0000000..351917a --- /dev/null +++ b/src/parse_args/mod.rs @@ -0,0 +1,50 @@ +use std::process; + +use clap::Parser; +use tracing::error; + +#[derive(Parser, Debug, Clone)] +#[clap(about, version, author)] + +pub struct CliArgs { + /// Docker update interval in ms, minimum 1, reccomended 500+ + #[clap(short = 'd', default_value_t = 1000)] + pub docker: u32, + + /// Don't draw gui - for debugging - mostly pointless + #[clap(short = 'g')] + pub gui: bool, + + /// Remove timestamps from Docker logs + #[clap(short = 't')] + pub timestamp: bool, + + /// Show raw logs, default is to remove ansi formatting + #[clap(short = 'r', conflicts_with = "color")] + pub raw: bool, + + /// Attempt to colorize the logs + #[clap(short = 'c', conflicts_with = "raw")] + pub color: bool, +} + +impl CliArgs { + /// Parse cli arguments + pub fn new() -> Self { + let args = CliArgs::parse(); + + // Quit the program if the docker update argument is 0 + // Should maybe change it to check if less than 100 + if args.docker == 0 { + error!("docker args needs to be greater than 0"); + process::exit(1) + } + Self { + color: args.color, + docker: args.docker, + gui: !args.gui, + raw: args.raw, + timestamp: !args.timestamp, + } + } +} diff --git a/src/ui/color_match.rs b/src/ui/color_match.rs new file mode 100644 index 0000000..14543b6 --- /dev/null +++ b/src/ui/color_match.rs @@ -0,0 +1,77 @@ +pub mod log_sanitizer { + + use cansi::{categorise_text, Color as CansiColor, Intensity}; + use tui::{ + style::{Color, Modifier, Style}, + text::{Span, Spans}, + }; + + /// Attempt to colorize the given string to tui-rs standars + pub fn colorize_logs(input: String) -> Vec> { + vec![Spans::from( + categorise_text(&input) + .into_iter() + .map(|i| { + let fg_color = color_ansi_to_tui(i.fg_colour); + let bg_color = color_ansi_to_tui(i.bg_colour); + let style = Style::default().bg(bg_color).fg(fg_color); + if i.blink { + style.add_modifier(Modifier::SLOW_BLINK); + } + if i.underline { + style.add_modifier(Modifier::UNDERLINED); + } + if i.reversed { + style.add_modifier(Modifier::REVERSED); + } + if i.intensity == Intensity::Bold { + style.add_modifier(Modifier::BOLD); + } + if i.hidden { + style.add_modifier(Modifier::HIDDEN); + } + if i.strikethrough { + style.add_modifier(Modifier::CROSSED_OUT); + } + Span::styled(i.text.to_owned(), style) + }) + .collect::>(), + )] + } + + /// Remove all ansi formatting from a given string and create tui-rs spans + pub fn remove_ansi(input: String) -> Vec> { + let mut output = String::from(""); + for i in categorise_text(&input) { + output.push_str(i.text) + } + raw(output) + } + + /// create tui-rs spans that exactly match the given strings + pub fn raw(input: String) -> Vec> { + vec![Spans::from(Span::raw(input))] + } + + /// Change from ansi to tui colors + fn color_ansi_to_tui(color: CansiColor) -> Color { + match color { + CansiColor::Black => Color::Black, + CansiColor::Red => Color::Red, + CansiColor::Green => Color::Green, + CansiColor::Yellow => Color::Yellow, + CansiColor::Blue => Color::Blue, + CansiColor::Magenta => Color::Magenta, + CansiColor::Cyan => Color::Cyan, + CansiColor::White => Color::White, + CansiColor::BrightBlack => Color::Black, + CansiColor::BrightRed => Color::LightRed, + CansiColor::BrightGreen => Color::LightGreen, + CansiColor::BrightYellow => Color::LightYellow, + CansiColor::BrightBlue => Color::LightBlue, + CansiColor::BrightMagenta => Color::LightMagenta, + CansiColor::BrightCyan => Color::LightCyan, + CansiColor::BrightWhite => Color::White, + } + } +} diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs new file mode 100644 index 0000000..8bdda8d --- /dev/null +++ b/src/ui/draw_blocks.rs @@ -0,0 +1,598 @@ +use parking_lot::Mutex; +use std::default::Default; +use std::{fmt::Display, sync::Arc}; +use tui::{ + backend::Backend, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + symbols, + text::{Span, Spans}, + widgets::{ + Axis, Block, BorderType, Borders, Chart, Clear, Dataset, GraphType, List, ListItem, + Paragraph, + }, + Frame, +}; + +use crate::{ + app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats}, + app_error::AppError, +}; + +use super::{GuiState, SelectablePanel}; + +const NAME_TEXT: &str = r#" + 88 + 88 + 88 + ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, +a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 +8b d8 )888( 8888[ 8PP""""""" 88 +"8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 + `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 "#; + +const NAME: &str = env!("CARGO_PKG_NAME"); +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const REPO: &str = env!("CARGO_PKG_REPOSITORY"); +const ORANGE: Color = Color::Rgb(255, 178, 36); +const MARGIN: &str = " "; + +/// Generate block, add a bored if is the selected panel, +/// add custom title based on state of each panel +fn generate_block<'a>( + selectable_panel: Option, + app_data: &Arc>, + selected_panel: &SelectablePanel, +) -> Block<'a> { + let mut block = Block::default().borders(Borders::ALL); + + if let Some(panel) = selectable_panel { + let title = match panel { + SelectablePanel::Containers => { + format!( + " {} {} ", + panel.title(), + app_data.lock().containers.get_state_title() + ) + } + SelectablePanel::Logs => { + format!(" {} {} ", panel.title(), app_data.lock().get_log_title()) + } + _ => String::from(""), + }; + block = block.title(title); + if selected_panel == &panel { + let selected_style = Style::default().fg(Color::LightCyan); + let selected_border = BorderType::Plain; + block = block + .border_style(selected_style) + .border_type(selected_border); + } + } + block +} + +/// Draw the selectable panels +pub fn draw_commands( + app_data: &Arc>, + area: Rect, + f: &mut Frame<'_, B>, + gui_state: &Arc>, + index: Option, + selected_panel: &SelectablePanel, +) { + let panel = SelectablePanel::Commands; + let block = generate_block(Some(panel), app_data, selected_panel); + + gui_state.lock().insert_into_area_map(panel, area); + + if let Some(i) = index { + let items = app_data.lock().containers.items[i] + .docker_controls + .items + .iter() + .map(|i| { + let lines = Spans::from(vec![Span::styled( + i.to_string(), + Style::default().fg(i.get_color()), + )]); + ListItem::new(lines) + }) + .collect::>(); + + let items = List::new(items) + .block(block) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)) + .highlight_symbol("▶ "); + + f.render_stateful_widget( + items, + area, + &mut app_data.lock().containers.items[i].docker_controls.state, + ); + } else { + let debug_text = String::from(""); + let paragraph = Paragraph::new(debug_text) + .block(block) + .alignment(Alignment::Center); + f.render_widget(paragraph, area) + } +} + +/// Draw the selectable panels +pub fn draw_containers( + app_data: &Arc>, + area: Rect, + f: &mut Frame<'_, B>, + gui_state: &Arc>, + selected_panel: &SelectablePanel, + widths: &Columns, +) { + let panel = SelectablePanel::Containers; + let block = generate_block(Some(panel), app_data, selected_panel); + + gui_state.lock().insert_into_area_map(panel, area); + + let items = app_data + .lock() + .containers + .items + .iter() + .map(|i| { + let state_style = Style::default().fg(i.state.get_color()); + let blue = Style::default().fg(Color::Blue); + + let mems = format!( + "{:>1} / {:>1}", + i.mem_stats.back().unwrap_or(&ByteStats::new(0)), + i.mem_limit + ); + + let lines = Spans::from(vec![ + Span::styled( + format!("{:width$}", MARGIN, i.status, width = widths.status.1), + state_style, + ), + Span::styled( + format!( + "{}{:>width$}", + MARGIN, + i.cpu_stats.back().unwrap_or(&CpuStats::new(0.0)), + width = widths.cpu.1 + ), + state_style, + ), + Span::styled( + format!("{}{:>width$}", MARGIN, mems, width = widths.mem.1), + state_style, + ), + Span::styled( + format!("{}{:>width$}", MARGIN, i.name, width = widths.name.1), + blue, + ), + Span::styled( + format!("{}{:>width$}", MARGIN, i.image, width = widths.image.1), + blue, + ), + Span::styled( + format!("{}{:>width$}", MARGIN, i.net_rx, width = widths.net_rx.1), + Style::default().fg(Color::Rgb(255, 233, 193)), + ), + Span::styled( + format!("{}{:>width$}", MARGIN, i.net_tx, width = widths.net_tx.1), + Style::default().fg(Color::Rgb(205, 140, 140)), + ), + ]); + ListItem::new(lines) + }) + .collect::>(); + + if items.is_empty() { + let debug_text = String::from("no containers running"); + let paragraph = Paragraph::new(debug_text) + .block(block) + .alignment(Alignment::Center); + f.render_widget(paragraph, area) + } else { + let items = List::new(items) + .block(block) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)) + .highlight_symbol("⚪ "); + + f.render_stateful_widget(items, area, &mut app_data.lock().containers.state); + } +} + +/// Draw the selectable panels +pub fn draw_logs( + app_data: &Arc>, + area: Rect, + f: &mut Frame<'_, B>, + gui_state: &Arc>, + index: Option, + selected_panel: &SelectablePanel, +) { + let panel = SelectablePanel::Logs; + + gui_state.lock().insert_into_area_map(panel, area); + + let block = generate_block(Some(panel), app_data, selected_panel); + + let init = app_data.lock().init; + if !init { + let icon = gui_state.lock().get_loading(); + let parsing_logs = format!("parsing logs {}", icon); + let paragraph = Paragraph::new(parsing_logs) + .style(Style::default()) + .block(block) + .alignment(Alignment::Center); + f.render_widget(paragraph, area) + + } else if let Some(index) = index { + let items = app_data.lock().containers.items[index] + .logs + .items + .iter() + .enumerate() + .map(|i| i.1.to_owned()) + .collect::>(); + + let items = List::new(items) + .block(block) + .highlight_symbol("▶ ") + .highlight_style(Style::default().add_modifier(Modifier::BOLD)); + f.render_stateful_widget( + items, + area, + &mut app_data.lock().containers.items[index].logs.state, + ); + } else { + let debug_text = String::from("no logs found"); + let paragraph = Paragraph::new(debug_text) + .block(block) + .alignment(Alignment::Center); + f.render_widget(paragraph, area) + } +} + +/// Draw the cpu + mem charts +pub fn draw_chart( + f: &mut Frame<'_, B>, + area: Rect, + app_data: &Arc>, + index: Option, +) { + if let Some(index) = index { + let area = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(area); + let (cpu, mem) = app_data.lock().containers.items[index].get_chart_data(); + + let cpu_dataset = vec![Dataset::default() + .marker(symbols::Marker::Dot) + .style(Style::default().fg(Color::Magenta)) + .graph_type(GraphType::Line) + .data(&cpu.0)]; + + let mem_dataset = vec![Dataset::default() + .marker(symbols::Marker::Dot) + .style(Style::default().fg(Color::Cyan)) + .graph_type(GraphType::Line) + .data(&mem.0)]; + let cpu_chart = make_chart( + cpu.2, + String::from("cpu"), + cpu_dataset, + CpuStats::new(cpu.0.last().unwrap_or(&(0.00, 0.00)).1), + cpu.1, + ); + let mem_chart = make_chart( + mem.2, + String::from("memory"), + mem_dataset, + ByteStats::new(mem.0.last().unwrap_or(&(0.0, 0.0)).1 as u64), + mem.1, + ); + + f.render_widget(cpu_chart, area[0]); + f.render_widget(mem_chart, area[1]); + } +} + +/// Create charts +fn make_chart( + state: State, + name: String, + dataset: Vec, + current: T, + max: T, +) -> Chart { + let title_color = match state { + State::Running => Color::Green, + _ => state.get_color(), + }; + let label_color = match state { + State::Running => ORANGE, + _ => state.get_color(), + }; + Chart::new(dataset) + .block( + Block::default() + .title_alignment(Alignment::Center) + .title(Span::styled( + format!(" {} {} ", name, current), + Style::default() + .fg(title_color) + .add_modifier(Modifier::BOLD), + )) + .borders(Borders::ALL) + .border_type(BorderType::Plain), + ) + .x_axis( + Axis::default() + .style(Style::default().fg(title_color)) + .bounds([0.00, 60.0]), + ) + .y_axis( + Axis::default() + .labels(vec![ + Span::styled("", Style::default().fg(label_color)), + Span::styled( + format!("{}", max), + Style::default() + .add_modifier(Modifier::BOLD) + .fg(label_color), + ), + ]) + // add 0.01, for cases when the value is 0 + .bounds([0.0, max.get_value() +0.01]), + ) +} + +/// Show error popup over whole screen +pub fn draw_info_bar( + area: Rect, + columns: &Columns, + f: &mut Frame<'_, B>, + has_containers: bool, + info_visible: bool, +) { + let block = || Block::default().style(Style::default().bg(Color::Magenta).fg(Color::Black)); + + f.render_widget(block(), area); + + let mut column_headings = format!(" {:>width$}", columns.state.0, width = columns.state.1); + column_headings.push_str( + format!( + "{} {:>width$}", + MARGIN, + columns.status.0, + width = columns.status.1 + ) + .as_str(), + ); + column_headings + .push_str(format!("{}{:>width$}", MARGIN, columns.cpu.0, width = columns.cpu.1).as_str()); + column_headings + .push_str(format!("{}{:>width$}", MARGIN, columns.mem.0, width = columns.mem.1).as_str()); + column_headings.push_str( + format!( + "{}{:>width$}", + MARGIN, + columns.name.0, + width = columns.name.1 + ) + .as_str(), + ); + column_headings.push_str( + format!( + "{}{:>width$}", + MARGIN, + columns.image.0, + width = columns.image.1 + ) + .as_str(), + ); + column_headings.push_str( + format!( + "{}{:>width$}", + MARGIN, + columns.net_rx.0, + width = columns.net_rx.1 + ) + .as_str(), + ); + column_headings.push_str( + format!( + "{}{:>width$}", + MARGIN, + columns.net_tx.0, + width = columns.net_tx.1 + ) + .as_str(), + ); + + let suffix = if info_visible { "exit" } else { "show" }; + let info_text = format!("( h ) to {} help {}", suffix, MARGIN); + let info_width = info_text.chars().count(); + + let column_width = column_headings.chars().count(); + + let splits = if has_containers { + vec![ + Constraint::Min(column_width as u16), + Constraint::Min(info_width as u16), + ] + } else { + vec![Constraint::Percentage(100)] + }; + + let split_bar = Layout::default() + .direction(Direction::Horizontal) + .constraints(splits.as_ref()) + .split(area); + + if has_containers { + let paragraph = Paragraph::new(column_headings) + .block(block()) + .alignment(Alignment::Left); + f.render_widget(paragraph, split_bar[0]); + } + + let paragraph = Paragraph::new(info_text) + .block(block()) + .alignment(Alignment::Right); + + let index = if has_containers { 1 } else { 0 }; + f.render_widget(paragraph, split_bar[index]); +} + +/// Show error popup over whole screen +pub fn draw_help_box(f: &mut Frame<'_, B>) { + let title = format!(" {} ", VERSION); + + let mut description_text = + String::from("\n A basic docker container information viewer and controller."); + description_text.push_str("\n Tab or Alt+Tab to change panels, arrows to change lines, enter to send docker container commands."); + description_text.push_str("\n Mouse input also available."); + description_text.push_str("\n ( q ) to quit at any time."); + description_text + .push_str("\n\n currenty an early work in progress, all and any input appreciated"); + description_text.push_str(format!("\n {}", REPO.trim()).as_str()); + + let mut max_line_width = 0; + + let all_text = format!("{}{}", NAME_TEXT, description_text); + + all_text.lines().into_iter().for_each(|line| { + let width = line.chars().count(); + if width > max_line_width { + max_line_width = width; + } + }); + + let mut lines = all_text.lines().count(); + + // Add some vertical and horizontal padding to the info box + lines += 3; + max_line_width += 4; + + let name_paragraph = Paragraph::new(NAME_TEXT) + .style(Style::default().bg(Color::Magenta).fg(Color::White)) + .block(Block::default()) + .alignment(Alignment::Center); + + let description_paragraph = Paragraph::new(description_text.as_str()) + .style(Style::default().bg(Color::Magenta).fg(Color::Black)) + .block(Block::default()) + .alignment(Alignment::Left); + + let block = Block::default() + .title(title) + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .border_style(Style::default().fg(Color::Black)); + + let area = centered_info(lines as u16, max_line_width as u16, f.size()); + + let split_popup = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Max(NAME_TEXT.lines().count() as u16), + Constraint::Max(description_text.lines().count() as u16), + ] + .as_ref(), + ) + .split(area); + + // Order is important here + f.render_widget(Clear, area); + f.render_widget(name_paragraph, split_popup[0]); + f.render_widget(description_paragraph, split_popup[1]); + f.render_widget(block, area); +} + +/// Show error popup over whole screen +pub fn draw_error(f: &mut Frame<'_, B>, error: AppError, seconds: Option) { + let block = Block::default() + .title(" Error ") + .border_type(BorderType::Rounded) + .title_alignment(Alignment::Center) + .borders(Borders::ALL); + + let to_push = match error { + AppError::DockerConnect => { + format!( + "\n\n {}::v{} closing in {:02} seconds", + NAME, + VERSION, + seconds.unwrap_or(5) + ) + } + _ => String::from("\n\n ( c ) to clear error\n ( q ) to quit oxker"), + }; + + let mut text = format!("\n{}", error); + + text.push_str(to_push.as_str()); + + let mut max_line_width = 0; + text.lines().into_iter().for_each(|line| { + let width = line.chars().count(); + if width > max_line_width { + max_line_width = width; + } + }); + + let mut lines = text.lines().count(); + + // Add some horizontal & vertical margins + max_line_width += 8; + lines += 3; + + let paragraph = Paragraph::new(text) + .style(Style::default().bg(Color::Red).fg(Color::White)) + .block(block) + .alignment(Alignment::Center); + + let area = centered_info(lines as u16, max_line_width as u16, f.size()); + f.render_widget(Clear, area); + f.render_widget(paragraph, area); +} + +/// draw a box in the center of the screen, based on max line width + number of lines +fn centered_info(number_lines: u16, max_line_width: u16, r: Rect) -> Rect { + // This can panic if number_lines or max_line_width is larger than r.height or r.width + let blank_vertical = (r.height - number_lines) / 2; + let blank_horizontal = (r.width - max_line_width) / 2; + + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Max(blank_vertical), + Constraint::Max(number_lines), + Constraint::Max(blank_vertical), + ] + .as_ref(), + ) + .split(r); + + Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Max(blank_horizontal), + Constraint::Max(max_line_width), + Constraint::Max(blank_horizontal), + ] + .as_ref(), + ) + .split(popup_layout[1])[1] +} diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs new file mode 100644 index 0000000..39e3d40 --- /dev/null +++ b/src/ui/gui_state.rs @@ -0,0 +1,161 @@ +use std::{collections::HashMap, fmt}; +use tui::layout::Rect; + +#[derive(Debug, PartialEq, std::hash::Hash, std::cmp::Eq, Clone, Copy)] +pub enum SelectablePanel { + Containers, + Commands, + Logs, +} +#[derive(Debug)] +pub enum Loading { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, +} + +impl Loading { + pub fn next(&self) -> Self { + match self { + Self::One => Self::Two, + Self::Two => Self::Three, + Self::Three => Self::Four, + Self::Four => Self::Five, + Self::Five => Self::Six, + Self::Six => Self::Seven, + Self::Seven => Self::Eight, + Self::Eight => Self::Nine, + Self::Nine => Self::Ten, + Self::Ten => Self::One, + // Self::Five => Self::One + } + } +} +// "⠋", +// "⠙", +// "⠹", +// "⠸", +// "⠼", +// "⠴", +// "⠦", +// "⠧", +// "⠇", +// "⠏" + +impl fmt::Display for Loading { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let disp = match self { + Self::One => "⠋", + Self::Two => "⠙", + Self::Three => "⠹", + Self::Four => "⠸", + Self::Five => "⠼", + Self::Six => "⠴", + Self::Seven => "⠦", + Self::Eight => "⠧", + Self::Nine => "⠇", + Self::Ten => "⠏", + }; + write!(f, "{}", disp) + } +} + +impl SelectablePanel { + pub fn title(self) -> &'static str { + match self { + Self::Containers => "Containers", + Self::Logs => "Logs", + _ => "", + } + } + pub fn next(self) -> Self { + match self { + Self::Containers => Self::Commands, + Self::Commands => Self::Logs, + Self::Logs => Self::Containers, + } + } + pub fn prev(self) -> Self { + match self { + Self::Containers => Self::Logs, + Self::Commands => Self::Containers, + Self::Logs => Self::Commands, + } + } +} + +/// Global gui_state, stored in an Arc +#[derive(Debug)] +pub struct GuiState { + // Think this should be a BMapTree, so can define order when iterating over potential intersects + // Is an issue if two panels are in the same space, sush as a smaller panel embedded, yet infront of, a larger panel + // If a BMapTree think it would mean have to implement ordering for SelectablePanel + area_map: HashMap, + loading: Loading, + pub selected_panel: SelectablePanel, + pub show_help: bool, +} + +impl GuiState { + /// Generate a default gui_state + pub fn default() -> Self { + Self { + area_map: HashMap::new(), + loading: Loading::One, + selected_panel: SelectablePanel::Containers, + show_help: false, + } + } + + /// clear panels hash map, so on resize can fix the sizes for mouse clicks + pub fn clear_area_map(&mut self) { + self.area_map.clear(); + } + + /// Check if a given Rect (a clicked area of 1x1), interacts with any known panels + pub fn rect_insersects(&mut self, rect: Rect) { + if let Some(data) = self + .area_map + .iter() + .filter(|i| i.1.intersects(rect)) + .collect::>() + .get(0) + { + self.selected_panel = *data.0; + } + } + + /// Insert selectable gui panel into area map + pub fn insert_into_area_map(&mut self, panel: SelectablePanel, area: Rect) { + self.area_map.entry(panel).or_insert(area); + } + + /// Change to next selectable panel + pub fn next_panel(&mut self) { + self.selected_panel = self.selected_panel.next(); + } + + /// Change to previous selectable panel + pub fn previous_panel(&mut self) { + self.selected_panel = self.selected_panel.prev(); + } + + pub fn next_loading(&mut self) { + self.loading = self.loading.next() + } + + pub fn get_loading(&mut self) -> String { + self.loading.to_string() + } + + pub fn reset_loading(&mut self) { + self.loading = Loading::One; + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..a7b28f1 --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,215 @@ +use anyhow::Result; +use crossterm::{ + event::{self, DisableMouseCapture, EnableMouseCapture, Event}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use parking_lot::Mutex; +use std::sync::atomic::AtomicBool; +use std::{ + io, + sync::{atomic::Ordering, Arc}, +}; +use tokio::sync::broadcast::Sender; +use tui::{ + backend::{Backend, CrosstermBackend}, + layout::{Constraint, Direction, Layout}, + Frame, Terminal, +}; + +mod color_match; +mod draw_blocks; +mod gui_state; + +pub use self::color_match::*; +pub use self::gui_state::{GuiState, SelectablePanel}; +use crate::{app_data::AppData, app_error::AppError, input_handler::InputMessages}; +use draw_blocks::*; + +/// Take control of the terminal in order to draw gui +pub async fn create_ui( + app_data: Arc>, + sender: Sender, + is_running: Arc, + gui_state: Arc>, +) -> Result<()> { + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let res = run_app(&mut terminal, app_data, sender, is_running, gui_state).await; + + disable_raw_mode().unwrap(); + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor().unwrap(); + + if let Err(err) = res { + err.disp() + } + Ok(()) +} + +/// Run a loop to draw the gui +async fn run_app( + terminal: &mut Terminal, + app_data: Arc>, + sender: Sender, + is_running: Arc, + gui_state: Arc>, +) -> Result<(), AppError> { + let input_poll_rate = std::time::Duration::from_millis(75); + + // Check for docker connect errors before attempting to draw the gui + let e = app_data.lock().get_error(); + if let Some(error) = e { + if let AppError::DockerConnect = error { + let mut seconds = 5; + loop { + if seconds < 1 { + is_running.store(false, Ordering::SeqCst); + break; + } + terminal + .draw(|f| draw_error(f, AppError::DockerConnect, Some(seconds))) + .unwrap(); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + seconds -= 1; + } + } + } else { + loop { + terminal.draw(|f| ui(f, &app_data, &gui_state)).unwrap(); + if crossterm::event::poll(input_poll_rate).unwrap() { + let event = event::read().unwrap(); + if let Event::Key(key) = event { + sender + .send(InputMessages::ButtonPress(key.code)) + .unwrap_or(0); + } else if let Event::Mouse(m) = event { + sender.send(InputMessages::MouseEvent(m)).unwrap_or(0); + } else if let Event::Resize(_, _) = event { + gui_state.lock().clear_area_map(); + terminal.autoresize().unwrap_or(()); + } + } + + if !is_running.load(Ordering::SeqCst) { + break; + } + } + } + Ok(()) +} + +fn ui( + f: &mut Frame<'_, B>, + app_data: &Arc>, + gui_state: &Arc>, +) { + // set max height for container section, needs +4 to deal with docker commands list and borders + let mut height = app_data.lock().get_container_len(); + if height < 12 { + height += 4; + } else { + height = 12 + } + + let column_widths = app_data.lock().get_width(); + let has_containers = !app_data.lock().containers.items.is_empty(); + let has_error = app_data.lock().get_error(); + let log_index = app_data.lock().get_selected_log_index(); + let selected_panel = gui_state.lock().selected_panel; + let show_help = gui_state.lock().show_help; + + let whole_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(1), Constraint::Min(100)].as_ref()) + .split(f.size()); + + // Split into 3, containers+controls, logs, then graphs + let upper_main = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Max(height as u16), Constraint::Percentage(50)].as_ref()) + .split(whole_layout[1]); + + let top_split = if has_containers { + vec![Constraint::Percentage(90), Constraint::Percentage(10)] + } else { + vec![Constraint::Percentage(100)] + }; + // Containers + docker commands + let top_panel = Layout::default() + .direction(Direction::Horizontal) + .constraints(top_split.as_ref()) + .split(upper_main[0]); + + let lower_split = if has_containers { + vec![Constraint::Percentage(75), Constraint::Percentage(25)] + } else { + vec![Constraint::Percentage(100)] + }; + + // Split into 3, containers+controls, logs, then graphs + let lower_main = Layout::default() + .direction(Direction::Vertical) + .constraints(lower_split.as_ref()) + .split(upper_main[1]); + + draw_containers( + app_data, + top_panel[0], + f, + gui_state, + &selected_panel, + &column_widths, + ); + + if has_containers { + draw_commands( + app_data, + top_panel[1], + f, + gui_state, + log_index, + &selected_panel, + ); + } + + draw_logs( + app_data, + lower_main[0], + f, + gui_state, + log_index, + &selected_panel, + ); + + draw_info_bar( + whole_layout[0], + &column_widths, + f, + has_containers, + show_help, + ); + + // only draw charts if there are containers + if has_containers { + draw_chart(f, lower_main[1], app_data, log_index); + } + + // Check if error, and show popup if so + if show_help { + draw_help_box(f); + } + + if let Some(error) = has_error { + app_data.lock().show_error = true; + draw_error(f, error, None); + } +}