From 7dadd2290fc0fbd1f56f2369c508f122e74b4379 Mon Sep 17 00:00:00 2001 From: yukkop Date: Sat, 23 Aug 2025 16:37:08 +0000 Subject: [PATCH] feat(package): `shellplot`: created --- package/default.nix | 1 + package/server-health/server-health.sh | 1 - package/shellplot/default.nix | 7 ++ package/shellplot/shellplot.sh | 90 +++++++++++++++++++++++++ plot.png | Bin 0 -> 21508 bytes 5 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 package/shellplot/default.nix create mode 100644 package/shellplot/shellplot.sh create mode 100644 plot.png diff --git a/package/default.nix b/package/default.nix index 5262117..fa9f58e 100644 --- a/package/default.nix +++ b/package/default.nix @@ -274,4 +274,5 @@ in { support-bot = pkgs.callPackage ./support-bot {}; nix-derivation-hash = pkgs.callPackage ./nix-derivation-hash {}; server-health = pkgs.callPackage ./server-health {}; + shellplot = pkgs.callPackage ./shellplot {}; } diff --git a/package/server-health/server-health.sh b/package/server-health/server-health.sh index 5edf290..a6cb500 100644 --- a/package/server-health/server-health.sh +++ b/package/server-health/server-health.sh @@ -70,4 +70,3 @@ esac len=$(wc -c <"$tmp" | awk '{print $1}') printf 'HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %s\r\nConnection: close\r\n\r\n' "$status" "$ctype" "$len" cat "$tmp" - diff --git a/package/shellplot/default.nix b/package/shellplot/default.nix new file mode 100644 index 0000000..f2c0b39 --- /dev/null +++ b/package/shellplot/default.nix @@ -0,0 +1,7 @@ +{ imagemagick, bash, writeShellScriptBin }: +writeShellScriptBin "shellplot" '' + set -a + BIN_CONVERT=${imagemagick}/bin/convert + set +a + ${bash}/bin/sh ${./shellplot.sh} +'' diff --git a/package/shellplot/shellplot.sh b/package/shellplot/shellplot.sh new file mode 100644 index 0000000..0fc52e4 --- /dev/null +++ b/package/shellplot/shellplot.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# ImageMagick: scatter plot "by dots" from (x y) data +# - Input: points.txt with "x y" per line (whitespace-separated) +# - Output: plot.png (white bg, black dots). Also draws an optional polyline through points. +# - Quick one-off example (manual coords): +# magick -size 640x400 xc:white -fill black -draw 'circle 100,100 102,100 circle 200,150 202,150 circle 320,240 322,240' out.png +# stdin→stdout by default; refuse to dump PNG to a tty + +W=800 H=600 M=40 R=3 +XAXIS=${1:-"X Axis"} +YAXIS=${2:-"Y Axis"} +IN=- +OUT=- + +BIN_CONVERT="${BIN_CONVERT:-convert}" + +CMD=$(command -v magick >/dev/null 2>&1 && printf "magick" || printf "%s" "$BIN_CONVERT") +FONT_FILE=${FONT_FILE:-$(fc-match -f '%{file}\n' 'DejaVu Sans:style=Book' 2>/dev/null)} +[ -r "$FONT_FILE" ] || { echo "No font found. Install dejavu_fonts and re-run." >&2; exit 1; } +POINTSIZE=${POINTSIZE:-12} + +# normalize IO +case "$IN" in + -|"" ) + INFILE=$(mktemp) || exit 1 + trap 'rm -f "$INFILE"' EXIT + cat >"$INFILE" # buffer stdin once + ;; + * ) + INFILE=$IN + ;; +esac + +if [ "$OUT" = "-" ]; then + [ -t 1 ] && { echo "Refusing to write PNG to terminal." >&2; exit 2; } + OUT=png:- +fi +# bounds +set -- $(awk 'NR==1{minx=maxx=$1; miny=maxy=$2} + {if($1maxx)maxx=$1; if($2maxy)maxy=$2} + END{print minx, maxx, miny, maxy}' "$INFILE") || exit 1 +minx=$1 maxx=$2 miny=$3 maxy=$4 +[ "$minx" = "$maxx" ] && maxx=$(awk -v v="$minx" 'BEGIN{print v+1}') +[ "$miny" = "$maxy" ] && maxy=$(awk -v v="$miny" 'BEGIN{print v+1}') + +sx=$(awk -v W=$W -v M=$M -v a=$minx -v b=$maxx 'BEGIN{printf "%.10f",(W-2*M)/(b-a)}') +sy=$(awk -v H=$H -v M=$M -v a=$miny -v b=$maxy 'BEGIN{printf "%.10f",(H-2*M)/(b-a)}') + +# dots + optional polyline +awk -v W=$W -v H=$H -v M=$M -v R=$R -v minx=$minx -v miny=$miny -v sx=$sx -v sy=$sy ' +BEGIN{ print "fill black"; print "stroke none" } +{ + px = M + ( $1 - minx ) * sx; + py = H - ( M + ( $2 - miny ) * sy ); + printf "circle %.2f,%.2f %.2f,%.2f\n", px, py, px+R, py; + pts = pts sprintf("%.2f,%.2f ", px, py); +} +END{ print "fill none"; print "stroke black"; printf "polyline %s\n", pts } +' "$INFILE" > /tmp/dots.mvg + +# axes + labels +awk -v W=$W -v H=$H -v M=$M \ + -v minx=$minx -v maxx=$maxx -v miny=$miny -v maxy=$maxy -v sx=$sx -v sy=$sy ' +BEGIN { + print "stroke black"; print "fill black"; + printf "line %d,%d %d,%d\n", M, H-M, W-M, H-M; # x axis + printf "line %d,%d %d,%d\n", M, H-M, M, M; # y axis + nticks=5 + for(i=0;i<=nticks;i++){ + xv=minx+(maxx-minx)*i/nticks; px=M+(xv-minx)*sx; py=H-M + printf "line %.2f,%.2f %.2f,%.2f\n", px, py, px, py+5 + printf "text %.2f,%.2f '"'"'%s'"'"'\n", px-10, py+20, xv + yv=miny+(maxy-miny)*i/nticks; px=M; py=H-(M+(yv-miny)*sy) + printf "line %.2f,%.2f %.2f,%.2f\n", px, py, px-5, py + printf "text %.2f,%.2f '"'"'%s'"'"'\n", px-35, py+5, yv + } + printf "text %.2f,%.2f '"'"'%s'"'"'\n", (W/2), H-5, "'"$XAXIS"'" + printf "text %.2f,%.2f '"'"'%s'"'"'\n", 15, 25, "'"$YAXIS"'" +} +' > /tmp/axes.mvg + +cat /tmp/axes.mvg /tmp/dots.mvg > /tmp/all.mvg + +# render +"$CMD" -size ${W}x${H} -font "$FONT_FILE" -pointsize "$POINTSIZE" \ + xc:white -draw @/tmp/all.mvg "$OUT" + +# log only when not writing to stdout +[ "$OUT" = "png:-" ] || printf 'wrote %s\n' "$OUT" >&2 + diff --git a/plot.png b/plot.png new file mode 100644 index 0000000000000000000000000000000000000000..92bfa0e492c11117ebc7a9777999768dd70ca8ec GIT binary patch literal 21508 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2Ti6VPIfLct33i0|SFXvPY0F14ES>14Ba# z1H&%{28M!6mzwSU>-M{6T)%&De-(cYmIHvu9raahrY5YtEOS+jIWT z&I8BacW&`e5ptZQ;yLNj0*@CAAVyF-Yln*Gq$N8xGE7wQoFrl{rT`Ltbd1Sml8R^0 zorngHNh+RQ(%b@`lTpAY+Hr!VCCF8}{qAm8P|M_+5POq}qw*J%iBfn0|N&Q+qO~rFkh=H*J z^TM>*vJ&!*WqY^TzuDuxIC=9Ai42L@m}+SoE7pC#<-+brOB?MAY?N6E@{(+T&i=BH zBr%(4WXQ#cEcTX5=nqT`n)r7ktKi<~d zc)#w&w`eJ*-A9heTX2-!*0+ma0WxmR(`ZJf33e*abH#a2?fG};&6?y{f2Zs|-^tUS z{jJG5uI(!K!V@Pi{$qHRMWUbFws&!m7_#8gSE&io z^P2v8AKLgmck4!t7%R(dQA?#ZSZs@{VVU6eO?*cB71?RIk#izt`_}O!A9KCFK8E)= z!_q$1cN`yly(X!6-db0)m+i^HIcc^^0gtEL$XfeoL*44jYyVX%?73Ng`?tS0v(=$Q z{=;#-iDzmL9K4vT+I9SM_GA^$NmImsMm0RiFur4KQha?Sukrf*7y0k$u;tu3w~y@* zzw86{AO9aQKUgbRXek6rPu2?)^e^ho`n+t?_LR4~*VR1x8l}4J*4^Bj?_d82-Ee;i zlPy@;PX%3}&YbtBx^sV3Nv(-xIxfd{^#92{Z`YrGxZZnH{e9214<`$5d|F>?#2dlX z8-8dWdy0M9$}1;~7-(;FEiquG?$pXM&_=V>~B z@7$g5XFNZ=nZJ-fe3FW%)*Q7roX7WZWtKj;k!PUT|9_f(#?hi%1$pTj+vWG{L?Id&6KqpZl@*0axS2f0g;$JI&K2 zB6M%9k(jvquU=TrOOVJpK@%W zcQ#$(?6Z_UCeQ2J_U1|+w@Z|YP-n{ag&&y>KCWBy*ZoU_knw`^%#qvQnn|YBu^+Z; z*!w&6?!Pk;J9ho#WAo{r*ELn{*KAOn3q`DVEx0|?;P>wfp4)VfOHNqU{^ZZH$Lra* z?V5dc;r)a2bsJAFe0%;w(b6-w*V`8#@8$;u;m*$w8VkNRT+rO6e_V1x@5>EEnb-aq zop<`UdhzMmS%*#?49K#Zk}$PkBl@nOLaYcG;QBawh}f_X?<&%tLp+UmbCpm zruuwy({2~(I0&_ZOXw}R@87ar4{K&^zIkl%F=6L<@0vgXs99*Uh2h=KX)XG82f*dM zS5|j^^F`?gJD8o+Ps)PJe9g|k795u@CvM-gaPpHruxV3{$kp-fy2`Ms)o+q@T#KIP zBo)<_s+Si8+0F>$%+lwYt5!bgfsrW4LpNVm+fJFsw)J_t-=yk!U`th3y7M>vW@mcq zq<+%Y{~#!^CSBO{SMjg;jB9eQ{)k!mzN_Q{mH1Oy>hAG&?Po2$*5HmQTg(N z=|!FF1Qqf5|4(OwDxoDRoBuFM+>rOPGbouP`WIWFn3nJSqQGI(kn*^!$f`lXsoVVnDh<%|_390E+CoZ=- zSo^oJ-bwwWw7iQ3$g7=GPcMvN{qfr*X~{{i(^hW*yIck#=J~zPTySAyupAgX{Mf=RW3IIQfkXB)~asz6;&C zDtF0_Yi`Q(3SO{>9$YhNx->Z`h=@>ZVzyt-YKCSFk7CTYMs%|*es1RPv`*UmS@zB;hs)P-mBFJyk2zB~bxra04F z_Gaw9zI5T_C-xAxAKbHvMQ=Axm#u)6=fBP1!d>V@oBxBYTm=soE}SgF15wu4BO{d? z9m+L#NkWC-E>Org=&fgx{@xb8j~65+9S8Pw&K|C~D3<;DoO72vuwVwYcT_tPjT7JB zO}tyA5vW%(tLdRB*aJ-!j}0vLS3T6VI}p49RI{{fSSe;>&R+Ldsbo^qQ%D>!9J|uL ztvX)J%JbkRh|3PlKKEtyME&Z8$|R+%JP&@>ivWe9!|eX{<7$rkmoA)qq#dHq;c;I7w%^}) z=c?F2a!tcMOD4ZOcGY^$xhi&$DB?=_98r;B`R7Z~3zd(M|4ba)G&&@_IfDWK+ZLY3-@-7M4QF^Sso%vF~=`R!>LW;1E18F)5{R=iDw%NP0XWfwG^F)5rKUSSf3tKIon z^+KfcDA>y@8O~&L6{{Z*T;Jw5DFTvcm_A(1%J?A{Vyj#-=|~d9gAIAj5AJa-3|5g_ z4=zpfffen~6WN05L!rT+g+%SYJ< zGv+&~d&WS@qn}THZw>e=x}q}Rg-YjXaEWwEo&PV}cNu08-nlA;Pa_((+-GsEYuvKs z#p=-iH?}RUcV23=F*h^wL+$4q-$buFZT>iO`+akT-{)VJ3x}+pP?_`DIWh3*h4@}j z0a4w3zRB=^>*MS;zezWWbR3RNYxLj?cGr$?QoX%nePO$FV$J3=wXysy{qN3NZSz-+ z*kMzAf5*On{pKHXUaWVYq~dvMu6&i_FWG=>#}_J{y2qKG+&Ne9`ES76Z)_xG9^rElWaJN@i`c(~C(o$34f?r-|`JkM%me!T|O zWkt^?N-wxAwZIE(;_igKKT@~Ue(qay+v{s%z5bj1YP!$o{x8>G@j)3tY zwS~5){Wu`Jx&Hi=Z2@bla`ygr-k>5ByH4O2sDV0Z+rx{u3hqZc+`ix`6yGSKu-=aG z`Sp&q%TssE*z@Mkn#aqRJ71RCP#9P8_jifKhp=>8P$c}A_Li@{r)}~&{<$iaPa+!D zL@{rE?fhiVi>fDbGtAkpmG-SWZ)>vaZ~W`%!-wQ9KJ|a*xObnyJL?K7riq0Y{~arL zS~>B;Ci5+l-17JMHtV+^+kU8R{i0;M8vbu9ByLX!S#hE+j$c*FK~~;whIZye50y!G z+m7)cII?@wH~EXq`}V55{wnof>K30|F2mmYr+4K(3dx8t|8{Tn?1Njx7oI<8`9Ayo z!HdBgFFDr-gA%ium0z>)t^>Y%ozy+~EYCHV*s8p@wk>jg_Vv~N0~hu$I(q!ttv7uhN*X8(fjL-tD?~VZHX8U(qjAPC^O-P5ymR?8m3Hc7+gAN^3Pkx{C)cIN6GAv3~Zh8^!|s#H5PB%E_hDr2bbLrt&Jjty9 z)rPVKf7Wfd{ze#)cfD7AGty@DEUa>dWW@j>A#>)2-v?OV@H*>w6*52kf=}uQGp8}{^ z*DT(4z?XZW$L|ZClV(HwB>Vc*L3ZuLD|0S*PMQyCeB3tt@i*gkP2mfbljp&u&8+#H8 zQUIv~Z=E_;yzsbU!Nm&~Jh`Ber@h#(b6aj4-`pi1AW^sNQx&tt|0s=ph9#2@9fu?! z$s8^A^XpsUpSSo0Sy(cIiiT8!H1FNtA1<6M;s>dg+46bXi*Gq~8d~qyPfZ1-MwQ%+ zFO4g98OB7}mrObYDYI`qI?uo8t=(xSblC8VjY`F!#22Mi1T2ritwL~cGPx|Uq|#3Pc=E$I!7~Wb4h%Xh^WvA-ol6!a z6RvqC4vEJmtX~+gGWUhb%Cq27NVM+t!*|IKw_RM= zd;%O$t@q|^elUOggAe9T>J!1qedUGMmw9HbZ8??dq&^XxWVF7#i)Ah^;hgIPX<2dY ztUQ|d&dmbU4`Bfpnky&#F48g4Vh`o#o$CT_S1oaRw=b?B{qhCPFOYIYH0tyHc1ivN z6SJMvFYX7ojzThOi}|mtWxRT<#cxqPxI9{UVb}kN9j6~DFRTy;^`1n%xcM7SGCzDF zb%1x_c|MpqC+x-^zKw@a&=fCp<)=FGiBWl&T4HAe>&li?Im}lFtXyN3H zY;f3Xg@~267&1TDv322O4eNVtps}XbQ~ckZs*G2J8kGp8LYljxTKzKocmJ_ywfZf3 z1MZWqyb$q8jwhZ~{R`Jz7t4>}zM!a-y=lAAeTTQN7Eabshxp|}MG5nrdj1PqB|=Y+ zK;nq&bl0H=XFoW8zu>t=7vc($U;kKDs~qRvS~xi)8WMOKHvjl+F34CYmk8x{UvKc3 zq@wv!*IuyhEt~p!?zt|v!7a%pORle%Ub3~ZYJa=mqPL#iE*Sko)ir*+5A2H{Tr*ub zIing9GC|QFMZU~tTE0itsI8(}n(*fV+zTi2se|?^$+>4^nSPyNX+uKgB{_y)i zZvBm>l@rQp-q>>5epT|Ev}DN#@9vD-50+iljPEQzobDqF*mgcw)Zs>sdjM+i?!yBKne<%r{w?JKXO|*iUWfB8JJ%C?_z%=Tjfd&PqCKc-`&Z#wQN~_hn?89zMK8Pto@5l z>t-KXB4cN)+x~guw1=9YzT~M_GrvEWcU^I9EXcH+bcbnQ`^DsMN}5RQ^6eLskB+X9 z)~T{DzJJBG*>wfS+Aer*QQoBM!1m94+nszN zot?r_=epO$8%o~$opt&cufck)Gd3z_|7PAbdjGnU=W^orsjdC`=RZ8mG$_x_v9+BI zZja4%{O)@DShKVG#l92U7*<3TG7aX;_?HQo|C0xOC(vp-fZ%!6mxA3S|EfIR#k&}OTccs3DmD+Ff7z=5=I>~Rl z&40gr>~?sm_~8coMX#mn8)r(cd%GhuCr2#h6~l_#h1QIFdDp9WPTIDq%67_4)?;^B z&%WcH>t?NVTXKTZ;>05T8@ZLw{uR3&KK**(U)v9Ef7iUP{;iR5jVUU%BK{)tBG+U4 z*KgZ?bX)HFAD~9wrq92pS+6(SX;~ulc7=?*%a8S2-zLX8A?Wv4Q7rKT_)o5yh0o_DUBb=Gc3zg6hu`+E1=X{FBUfpOrht+{f~54$@t%w0B; zR-K;?fxF35Os=hK4pL1l3C-B3P$KoT6^G$E#TT}xcpj*=N`MLAOviT1|7XFk0Rqm>Dw)?dk|2nMqzRhpZA4pl=vT3KS z%|G@p(IQrzTutDrO><$-ZR=a_s;gh9XmNm(g66_Ku`GUZ9IC5Z{1!DmXy#D$oHWJ2 z?0fT~xeMMOyzuh*h0G8E$Y2j=@}DUgxAz6UP|?x=Cl@b?_jNqS?HQKF@y&H%Jqigj z#Y=K>Kcv=qanE&OJqZy?JfwWF?El1tlUFD}>Sdurwx)?MqJ9(=zEIKffYd1mU8@f6 zEq-`=zLR>O<7aSf!D?POKjZeta~C{AE`XDONX+|11wSSSJlhTuf;7z?U6lfsaMYNx z_pK7N>gAdRPIg>bpSEOFCp^C3DZ&pPzgV)s?vIF->w=sY3SHcgn6x43&iMaqqr9iG+waDv*x$r|?{78HKJnSP;6cFA;(xh@}{#exO}MdFPY9#X1E)wekN zxiF~j|77n;Dw^xkZ23+{J3lvEI9Y=Q+;=&8^?ADU>9By6sV`KxAgNl!{`G=WZWU#U zb0W=4Bw8UU_EFloeH&Kj$lNn65o!gG$}HLOax?dvLbZ@OZG_Oy@BTafuDPHoG6|eG zA0@r>nX#LzFGI|#(+QHBV`{!yn(Pu>WyUerMG=x*_1^7ffA{6&b4^IkhBfbBvq7?; zjHOwLP-~b@34`Y(716r*`P`+~owx3GRu9Yor^A&8Y%j>{su8nl6>0=q#&zY;ErUu+ znWt~sH-JVwgBX8rQ@Qer>#9G;T$j!R;E}(O1#%MXvCCWi7P>s-0gZ5Tow0v^phc+Q zjoiY?8iC-vxbi^FefCwc%%;~`{T4wxvP_>J>@7I2_hY@^U;BElNh+RN0^Q~98+{+{ zuyRREu`V|Kk=t?zDE^!pRyFA+gN#@x#0eZ@CvFy-@K|0q22J<=%3Qg6Ri7 zesA$xVK7yWb+0CtyE+I{T^Y!)qRkpX)Dp zPFVp?R8#VQ&E?O`ci3C*tRA=s5>xvslk_V#@P8*{H_JVo#d|eeu`->ojPvxd(@;|o7&bez;BBUw|cIwlU z^7mbCr_FX&4>UTtkwNpCY8B&;Yx)hrai{8{qoitP9{xAaQmo7Wgw40Nlbz-Fsd!HM z^y(=4!)MwHPW)XsS!3ptOtvLo-^|%=I%mq(_n)5?yq&&bW9GAeh2rgYHJ+1Ho*unr z`Qc!E!MpnlCu`iy`Chc}o#=x{v$wCABaxf8o^g8GiW2F)jNa;wCmk=8R9>r{^E-Tt z?`uZoT~1FqYRYuC?3MiC|6;wvBo$BVtP`yl%lf}Ns|Rknna5ak+-S#Ry&G#q{{%3t zdc)|tu0bn$n|}nK_xmor^4DxNlYjorK7OD}*I%l$hIQ>T_vzri<%u zZG0W=c<~g-QBNWoMB;g78?8An#d+54P>=qCZw(Ru*Gf*)mzrE^|G#EN;?wRMkACYr zw4VGB)OPlNT=J%^sXr8)JSQ!2*_bD?=eI)74GF8xNr8RW8)AMietE-RFV8mLUh4J! z?NQGPYo%Ooi@7@-jg0&r{ARaX33YG>!rc#45q6ezPjDvVml~GPnEHy+`D+;h0%{kGBXSW-`%l~s$nq6 z6_&o9nvwIf<8spJyRf$28Rmc_@0p)pWzDdYo_AB=*9LGvy|!=N&e!}oy47z{%G9?! zCpL8%tYnwr|KRs5_j?(`u|uzt4DwF9Dy7e3J!;(Z`=jHG5+)xe3;BERc$d%RIc(;1 z_iobTbrsfM9(qqw$#m!WuXpR6D9D3awrL?s zU92@iZC-C%8hz`_+F$1K+^2s#T+9ntm%XO`^63qqVl}=RPdV|hFFcZInXA5=|M|NG zpn`V-Z|uS&wFXZFZv0+;A#=)_ZP5)a;MT$v3AXbMwXYBSs$a0<{Qm$s(VIVGK|Lzb zFFD-e@|hnO&fR=heIaOCPMW_-?*D=J{B3@VQXmth+p@1rQ+M8OX<8zrY7Nd}xz}F@ zKWMrY@aO#n&na&pwc711g_bt|)p@M!N`yq&z=cib>|-_ZxwoHvGUU3n4N}79R((#i zNLQP3Ll>k9Qp8?dCjRi{LW2aO1ja=*kc#@$#T(s=-`dHy`Yk$i2;6CWy5jo1f@9hn zPP;Cgykdg~W=C&{$mX4_BC_0nr4KYtc3K{o2<@(1+tGD#+ufH7C$BJojCgFjH;-$% z44bMv$6S}zPH-u;>Bb*X8yoI*8PZmrT#!{vsfPcKXWYJ5ud6j5(x}sd%1Jxg@k}dc*2vEq;p*L5ld9K08cqz55Sp`1C-^>XX%PAH2w3@MSZ| z1)sn}HGlV?&<=Pa^rMVdCeWxvO4JP;S}*_mt`W(#o+oM5$+a3XvZPt~WgcI1j<$ri;`FLbqTIYfrQc0&$FMd1V4>BFx^i;9S;%EQT{%NYSdSD`0WXdbK zHtVj#z5Q)`i$cI7A5$JJ3vKibeUKn;x55HkE_)TuKhyAcl~e0xC-p$B58&3($~W#i zUKdyFj(wrvB?|W6(hZXKhi3&AcznFzIYkQ`I6*hk`w!oH?(l2*!pRz;;5L8Is#$!o z=UT(nTlf}*fSWE;7Nx~9p5EW)EZ*t|7qZLe*X?ie0)>kfc>HTg$QH48dYs#B#jHAl zdcmI4%vLLf|O3DL)8mVb=7+0PF=ueVVxTsq1|nMi$lP(pg~UIajQ?`o9ogA&W~D4>gw3aEJa`S@FRreb~DJ$V4fpn)hRL=OQd+F zRO+rUU|*)wT6M23oD)2(w8Uw}ea3(#Hdl7*mk4PZgS&8bU^spSusCl$;(+Aak3jnRjl8Mh0ZUZ@0pg9P2g<@znv)(%mVFL*9_10GopQr!4T zTBBN|C!WpsRJ)&YDtKyoiPNL&%!_j$|8`cNSOhMiLp*H4`F7o5$?R|OQ+j#^)U{eE z5Me9x%jLltZYTAL+s;CSe&jlC$-SjjB9wU>VrAp={jImFoo@ZQ;JG9hocFzovi~*t z&u`m&y46o97d&tsH0k%_pBcAvl3u7>`UdV(E>S7J<+0*7zs)7f5~0lB;0~)-Qnvfy z_O%c1;5r!ZR4Kna>^E*2q#_m zHSfc&>i&J>)xDED+bNYNaKqDoy^y*k-0ao)Kuw+e5HDXhNV^6c>< ze#SrFLtjNoY|%1H3$@Rw8X`5@WWlX*z#n zw;tDAms7KI=C{~=w8^-m{o7n8hb{D&<6ZfLUk@XfouQ7cNS0~mI(uUcaDzj2`aJsstmct58kMQh4R4|>ttJ<~ z*11tD`sSA5w%o72A1>{<@yl5JK;Hbrbypwm*?0TjhOOO+W&RO=ic9u}_J* z-@3kFkT!R^oS*Qi@Zwi#wZo6UEtI z({m-2xECCnx1q-I+D3MJZu|JHb0LFsg^xvM1k2vjmbL2KQoLy{%y{Y5S(M?pdtNP5FH*MZgVxq5IFOKDaRN`19z3=aRY8 zADb$87gsp{OkU_Q|HiLfi#B}B(fBM8pnGfk&xnY7>|JHeMUsBoqjt+&``Qq+-=Wj) zNf#*pYF_PK%b+{$@GSKOPrfd^>|w2boJj@TpF;CM)YmizU2pSKddd%8{oxfb z@mN5L(w_}tR_}$ZdZ*ZfTZ@{GzAt2Qzt^jj2xy2b=S-jrMT6IpThp1w7 zo!6>#{Xlu{gAa)dFMIqv4j#x8x^P7G;1? zi;z!>WXW6h>tUcwTMw=zInzFAU;Nha5~S}uxWZMP!N0D-`If^?XD9WEpCBb$=M#Y) zn|WgX2ft7VItXdrC-jCS zSGDjdiRyrxDJ%w$LnEpgWQ>$cgjOajbyb?^@j~D>&kQzir3rFY9V(tdj8(hj_sBLX z&UI3s$OTymw$Qgx@ApAn`*uDhQ5$f%$~60`M3#Tc(WMI?t-6r81hUvHC}Dnh^R#Ro zyD3s&zc@wlmY;9Cy^cfHrHdbIdqdj#2G`3EHvDx`pZEwoYpl7z?(f3N+7+M~?VuVF zZdr#(Dwj|c_DMj5AgiG=DB^c z47+91KQ6qI0-6RENxwV4ZQbrRKgCvX0v2)qAHyWWP$N8R1l z_q17lc3vL5==ValkSvz1|D>%tMfyPbGd1~hR>Wl?vFYuW-HD$HB)%KgRC}L$KeJly z${)RwBGpap&kybmXMF0UJ`ub`ZOSC2^@m>>8dU4?{FS!*Be82Pzq(F)tbW^~)CF%; zZq!3)8*WVbQ@`g)cv@yxPPhg$DD+&#PDweEyEsW+SW3mkIZEMfd` zk#Xkz`Xeb8yBT9DC2RiZ%6t<0koQ90vGd9D#(*;0EAwQmIz_@jE2UfbHlA(B+@Qkr za$&Q_B%#y78{R2IY_mSof8dLz!9#V4tHO2fnS}LE*z9@MzTv)FhAro;zZ|9Uty1NV zzitL>VqOw@=C6Ccok-k2X1OX3ufB$L^AASeO?n+)I73GMkURT9B=h4XK! zXx{3q!)@jYdwDL{mCTy6wVMCjKCX4KB37Lu(^xxHG!1)OdFLipI!6TXq@8KuWKR4N zZSccRVzbug4_@90|CKJJvAww{|7t&{>AO}hafg-7if{k^aE&iD*|S9`?-%Q={w8y= zL-*R-{yA~azk2@g&*ul*pDuoFI_tps`wo+<7EVsx`XzWpg^*7)d#P88pK>dxSi2r|;v<7H3q4 zS*B$$yRUVs?!)HgOkG_34arr|*wZ$9Fif@V;M&>1}o@^_S3OSv)?D1vohb_;Y)X%Iryt5$4 zc*n=S3!Y0pfO2l-wwoMbtE+K;emv8+I7VGJ$Hn>! zzFiJ@TBT9VB$6%rD}(D^)2D*_CKc<9=2$Sv{pNl(U(zbHXx2UcS7oQ_ZlA7w@F>|y zy(e-Wn`pzz-A;Ea7hd*wSShaK8PsQ%-)8#z@FOXMbXy-u{`xxh!|_e=`OQzA8x%Kw zT-)ZfdtpJ4>yEb}D{T2!-Q{}f*Zk-=L+Ey%JL#ui@4MhG5r1{s;mrqfw>wYgyzoHB zqTJZ#0!P+%?zwt9bs}ee-gYe8Y5D0j@xB^GidXJRSanX3n8P@6iHKOG_%#_8@7D+J zoHa=0`g2v>?MsVeY{J2MqyGFd+cqx_oG><_+Me`sB~)6e1sF6<9hTyUQfv+OSG z_L4?^??X>T7rgEb*z5fwq%-%bbAi8Q#TzY)?QCCey$abTCVp^6_`}yq1y7b;$Xv{) z$L4;nHG1{CKl_#(0xiAg+H9NnHgUri#WUJ$m(vdZU^G}H{-;jfDD!tzl`@Z!HcfxeP1JVomR}{>OkX$Frbl;?5w$_2Q`yHn4bz1Ia@cQ9w@dqnj zFRYaQks@Gnk2TG#f&cj7O|AuJTsNGb{^6vO#ZIo6o!mCt1iaX4Uj67g7w}Jg#vPVd z{rrDby*dvvxlEdL(|E%>jfi_Z&!)F!eoj0jZ}6)2$6v)6Gv-xRoV}d5f6|32Ig{VK zulBKxQkj>&~CyEAa4~VaVnd$9tCPtWIsHR9__X#BB#I7)UMw55#xo$mCcsPTStXD!R!3 z{l99Ko?k3%_I%0rPTK4V;=i%qW=kYXHBaj=dFS5a3ocC!cqQTS+vE$Qrn%dagRRpK zpAdhzP_W=>{lYgZF06c%pX^_eXcF_=WX^ten?Mn*zH@(no7?i@|6g{dR1>_s#uY%X^DOw(DTx{e!=?7hF2|(XwsocITb# z2}#Bo7J7E`HC36p1w6G9W}j=A`uniGX~MOt877R!>e!v*n&NM#$VES4e-R=3Esndo zuf6TQ!&>o$r*s3Jm1x}3%$J;UWM2Ef`G;2?O?-I$!>JsLa%G>FTK6|sq* zLp)YTf0(bX%FQj{dCPM)e^V&?;YC&k)=ZO^ADC}{_`>xE*Vk=$xA@HC?|;R<-DJ^K zb6z|@VSD((SFr)#WHhcBez~X-==!hWy>h`H%?){F-;(22vy1z2&Nb;j*T^5=botal zM&$=@?GwIxfBtFwuf=?R^XK@c-DQot^V^fk-~Y)gE=n=J@t)~Lm3L!ZanPlW3=@~U z?U>SVwf*2DH-qI8J~LS#vmMy5J@G>AhMd*KyQA;2zU?{`XT0E}e!y?t71wxA#WEbV zX1wYoKXKB{XXy{$On&$`$}%UPJJYjlOI?iTgK|SQfkNf`wuGj=(vzLc@MMgAu zc-5Uhyel~Ir=7umEuVPK$Hxz?(0|yU{_y?2neEqgxPFJWUDIChGGoPc_ADFTm*x&9 zg%#J9yc?_%N{I;Gi_u4_ZddI!_3!ild zd<@g5wOUd;ce_N8Qsi#Is(ak?%bIQT8nX4;*?NupBW*b9UUHnj%V%HO|1CCH{M<}d z-Pg{YoF7l`u2kFe(NoN2cE0U< zp@z%k2WQqkuBp2nUKTGnQM!=d!^>v%^TQX?AAFto;n*~#KTFylPU73}|82m#>2q)W zHS3M#beCl}u5a5n-C?;!%=3xii{CYe#V-7M^utx<0Oi`_-wT66Gk%$ute5zevE&FS znDoqIIkfqk>!l8^d1R0&)+5W|e7~)6vr)h9C;JyS7{2LoRiA5V+wZV;`vNa>jbcR+ zTdOY@Jx=B(=S!ILSD81yb9ejwtxjlNy%e7;=k96jm&*PXhu!7)-qU=1U+eDt#@k*_ zC##+1|9)GW#~Z$fKi{l%{jx)WrN#g6neKOZ*_`mEBIBOQmx&&oCz)J=_TBg+A5m(e zbBj4`dfQ3v2j>qOtTE@Q(WvTmbNd~~_NRcIynS;&@5R4=_LqJ*`?KK6?+brCe|*Wz zFfsko_-=>4_CZhY2VI39Dy*ig+4%SSm)q=Cysf_ejuW$;b1mXjUA)d z_9jcwgi}Hi=1gt%T>I@9PwSnx*>`o`g>^P>wn|9xHs7^x%j$RDxq875LzVNjPD@2L ze~5hXTiqs>v##3UR{81W1!>|L|CmM2fB1iQ;p2zDj1wMcSZrmrxy9BM*785BZC~tz zz4i}!4mYUm<4bzhS>WM4qfV-)N^I8N9dAD_zsvMBuBAP^#ZOOFk6XZ#!DU~=*5wB$ z)h6VH&e$dN@0V~}J?DN~?$7b(ZT7t^xUrpUOAMp0oAX?|fVYw>3T&rXa9SvS5%jzj zsr|m~{_Ml6!yVqcUfj2N`QiV^oR&z)RJnY6Z~fn|B1t0TkJ=Z(C;h&*3w}Sp@Kw5^ zQZnWbQ$h5#!tCEpE1eU+%)anw&xMsskEAjxE$u4X@GU3e7UQ$4Et#(q|J*nDWc_0= zyHEVo%8I>@7uND-Y!#c;*KT#+>Fe|bht>vsDp^qef_ajNzqHq(7kMA@>?@L_TaRX{ zJHGFCUal*#|1I0^^KFNB9sF&&;F9ai?f;E9`LbAMcR5bvU3l3Gw8=wDr|Q72&xt<| z8tm8e=|9ktX|PHE&qu32wV}@*n%cGgEo;9vd*Mxy72k!gRPbKf?QpVKVXfl}=1Egl zn3*;uP2O~WTY9O4v3bd%rp+a$bFTCK`osBme;adu%SqP-|C29lw%by}KJULliI?Wa z$Oey-NlH08B&NypaZ4TSdGz6Xj)bkizY6JNBByQkJQ8|wf#H@&Ynt)Ge+(;ba%+_} zUlMnSy1(b>1eJzme-oURulal3mfb&2(khnmH}}0ia_9E*@l}h}?Xdd%>|k!<{Dljh z!CvwbkxqO(b;F~Kh#N9#)7m<@6V4<^T#|14Go`ZPWzmI&l3R3H&pvnjdw=1z-vK`r zHEK0QcG`Yn^wc`T7T#X_JpJu_W4HBltWLMbgmN;pGw)OnX`0Tmi zRY=4YzO?YxldB(`-)Qhg`^Q(yh~1i>f7E6lv6IWK)^vGV%KqGYJ2$+JUMTDZ%Ko<)C$8G?OD5tD8E{_!<6BFysh zk7<(^#QNUI)-{>Ou)Ci7YFty%cBhY*8)q%-_n0&#-1}aW*Z;G(_J3b&@Wg7tjlOq* zUc6!oL2MJ>C%pZ+VTtgWwcNqVAFi87d^F~%l6@vWwX){2-iG(@E*LTSsyWZy7x30} z#RXQcIoxw(es!pLvSxa4XPBF9+xI>B!`;Gwm4Po(f*x&R2+VrPkgeaiwEgfOH-ohn ze@vy_{&DT!&wF|MEWP!+1;1r;tnG5VJToAjA>=))=q|^T+>QQD@)J}vxA@LKIOp=h zBd!}>8#`RQ(7EJD8snlvPeUW>MRlyDo|(75Oieh%Bw;F1_n+b4y;oC~OQj#Kt9E!D zbRkFX%5UaX`}sxXnh3lnAp!$D$LDW^ULZ zduA1vaQML&_K7FL4W7(8bJ)9rJtk%?;fBsQ+IAQ+Xg{~}8 zzKyqP7rx34*rqLFEk3_h(Q{HrM?^zLyKC0rSKf&q9~vwd^wDH|oObv_eBy=M8@A*> z`=~45UdEfaKIuY*+8aB8Rr^?2sMK^25rj9>6+3TBB-4EY+X0Vm# z&sG69p0@jT4V9~n`}23Hzq!p5HLXqU^TKoQ1J;^{RB(93u+0$zC5TB=G;|z-E*{+Q zttz5S=}b27VAtXlb>H%ahHEVt#?K!<1D?lU56L`QV)2@sZnYA!Sw|* zXnV(|4Gfw+_Zq%#Kd|X?Vx9GjI9|0iO@Z}?GqiGca8H}h%x!hx%-sj?zZzsI@%)o>i=R?iarE4V z|8*CZslD0A_sYLv)_vFc69a#lc}PqBY5+yImX3qaMYS6@c*9~C{+Be({Fm{cbC-c5L_y5R-KjH?oA zqU{@t6JD%0cp&@W3(x(7krzH1+}N%7CXz?$T+>}Pr>SuP&+Ig+Twg2(?^rv|w4@`extC>vVn0E8G zubS`hQ+>e?Q-`+~+(D<5h}v*Zka4MN-ManIq|1qU)-$4b|3&dC|7*FwudOmaP451a z{EYqlTh=i7h8=vDoOsjdki~OKTDxO^W{;VELuWd+4DXF~YbYrU*Bb&|& zrX|K}-ZGs&*H|f=7^N$*U(oFz@BaS`m(9}T;=Y$;?6=>tiqrSH^Igt>zt$@*Fnir& zs|hZ+-v~NsAVTE!Me{qA!E1hKcnE)MnZ2%CZ~jMHo4*VuFZVNBzhHg6``{+-gnx<# zd*yr>+Ze;sU@-!Ok$Pp$l_?~Q-?+RREHOeqQ|*Shk8TeRBg^3QmUs-y+Nsa#s6+BUcB&s?ccBSbKj?Y*8eHJ z>g0EWeabSIjd#@@`E2kn1H6P^^XAiEQWG+nlTX+Cwk=md?0? znj3#YT?8`u@csTWw?+T!ivG$>*d?2MdUe{7Y2K!~JHU|_^l;L0^@YqjcP}jyFF3HY z@CT1*ocG7?i}yr#?U#RgH|Wn_jrY?Yy?3wNKIO>2zfX38mRC+%A{(0Wyj&5qHGU^} z+5vGskFS%c!g=2IN%LLuWxu`8zFT@R_jH@%<;I!Yr@rKNUe@Zj)+KOJ(6jsQ$HL3Z za+?mzow8Z?>F9FT+Hti8t?_>Me;w0|OOjc8Csw{`bO#A; z+G}6`>kf>}>x7e$6aM+V z_`<;G?c?Wfo5X)OI=c67oyeKr zmc{q4{9WK@8}VIr2`^u*iB?APX@`{9cgOZc%3ZUazw`Z46OYxb`e(RmRlG`O?`vKr zeyDA;@pt|Yf*hLJ(e?jy-g6$WWw4QZE&X4_M3eQipK9X2#>oN33diG^(rdZO(K`{p9zVBMMBA=!4e#Y?hvo}E z=vuYm_5FqYE{Y{@IpyDR2fH2YQN571Kcjwq^ya?1pFK5hir%{`{H&y1+^*|e{tT|k z*{ya$M-T6u`|#QCg}2_`mXA)?E!o3Yx1asl8#do#uD|2w$o|q0iTcj9asPI)e>4Bi zi011Rm0xe(R5{)G{;~r1*B4%jp4#^BPvw)igtU^M{i`2L*1P!hXLf=Q)890qEVd^P z+QFf@Omr!%R|JeeddQBmkY8^pZR-L?xfYXAL|o%5A418HAy?^_O(i> zFt3V_r!#&(lf5nf`}bG`tip|*8?v({J|({R z!j&3b|F7mU*NsmHK9t2K7Psx6yibB#*Qx!!(!w-{qU^I48TN+T>JF`3D6DyL&(^0_ zeNyF)Yt1+3$?sPA{eA95KDNE`R(qW?C!Ez>P`mE_x9Vk~O{bR~yEa=+>X*ip6Z2;9 zC+BzQ&9~Mqk!lU9DSY-dQhDJ`U4zdJYm&wDezHH=HFe{@pUMl)b#GYc^+GvtQRJ4V z_7!i+Yy(8QY`8PcU4N(faL(0Ji(o8DIeI6)X6nUVTQ9sYjM$Xw_3~u_+NNK zY+qk@e*8M|+v|&8t(PAx-TvlB-IPDuzW&~snw#_Uip)Cxtk1DIKU+_2RojA`F(Pk;FY%=y={jpqt88`eU&ln-Q3bQd(18rSgpAqw5R5iZOI;uFB3Hb z_kOgE*;8=(l%BzA#wo8Ptfo3u)+xpmD46_`>)XwF>b?A1`LfV!8>eoc;}@K$du1=r zH+!>fxkdLcxG!=EEf`E?X~e73;Hs=}BX8qNmEYsE?K|N(Q^_ za%3c=x4#ICh+#F$Ygv|^aLoJSztztVKAoFTXMf{MY&Nzo#f|oMV0NJKWpj3S;2`hsXw-< z-?7v;+gI`T%>McHeQm$&58cu&zh9jkpHZ%<^M)~puePMCJv4h!eZ1?r?se|(8Y`0% zV$&lo$d}c#@U7tF-q-p){1B`Ak$cu_S=OxQ?=C9*+rc%z(%pIS=?C4#35Q&E6xT=C zY3y2?IeX3i`P|;_+jX@$gI6EsJ)QhJt042m;y{kuzdk>orJnSd?Q2uF(Vy6|JM$J! zKEA|NY2t}<@73zRtYUxed%EuS^5lm{WcKa2y}$9@f6lpaoez%we%PhE{P|xirf>P8 zx4*s5G)pgSD*tt;?(W0y)|=lw-oAXn->nP#T|Q2`oVj3boAdo^Gwbb-=C_#_?_jUL zZ@cjFj0^6Io38y}oKX>e?Vobh0p-05o=*Ee!R%9DM!C|O{271igr4Uo96jAqydaaW zpKrzmnQLKZ{`}6|!aZfD+UpyOx)aW3Xw((FP!8IY`}+L+_0yd| z$8Uc~TYk~oGJo~+zw4%QH=jN>?Y92iZ#Uwv9S)3+wg2)_QA~d2t2eQmzQqQK@0&k; z>;AR-Cr-KkWx|~8p;GfI_d5N)kU7<36@TkptCV-%^)hvPg{1n={`#$1YuaPYx@w=q z>)U@tXZ%}y)P2>hYxC`0^O_>>JM8UFu3cMou;rSH&qqd1GiIin_8#jz+%@bCp0}R~ z^=RKR$4qN!&MwI?8?K}Om{a$k-IM!Ne8Wczjc}H(Gz;Ifzt$V_Y^TiS$h_+;-{=&* zlXHi-S9pt(c;)+FEyIFCd8U8 zf41~%I~RMaolxtRH{lu=`MgBW-`QW{J$v59Vy-p+L@tFnt=#Rb9`14b_vh#F*|QvO z)h~E*d7*In)B?~Fr%6i=X)p8xACeTZ13asadKTZQ$x6FZY8TZk3x9g!#+IUu|Jtq6 zU#ypZ-CuIL?$3TV8 zkvWKDgsbVR&}SI(LJI{EyK-2d36-SS^*@Z~h5K6l*NcVOCY+huE; z`Mp~HJyX%#3R+@#YI1DVVb|tG>z|3I=-KtX<95&e>HdG!rO5l$XLkKLTl7lk#)OAE zb(g;tjgE`7-)56u`6AzU5@@3TR_5<`sc5}wjhdU9yZ%(QNeZp&(EV4C@UQNlUOTh+ zk!$h47*_q8TzMw(xzkszh5I!eX>R-e z37#;}E_^Zn_KlB`8;|;OFM8X}fArTS^%S|f!}C78O5O00?Z?O67w&hdWJd4dh@CV= z-F#cyPw%AH;t23XpBxG_JSH$ey8_nNHM%S&9%efbn5zy+h zS!rjGjM8gkdu5g7`%nI>_mS=J;r`p}Iq86;fM<~0(y6T;d0Z2eW~OWK&0{h4(o{yzVT)vdmIDYY6N-EQZ*^OJ5bUw-hy!+_@-<{0y=<+)tn zu-BjKZ?5O0e~`t^lO7!dFU&!t-_P~YStll)HM3@8U|>)!ag8WRNi0dVN-j!GEJ