From 192331d27f1f72c35148fa0f88310d342d54b306 Mon Sep 17 00:00:00 2001 From: jetstream0 <49297268+jetstream0@users.noreply.github.com> Date: Wed, 2 Aug 2023 21:29:20 -0700 Subject: [PATCH] add ryuji and saki docs --- posts/meta.md | 2 +- posts/ryuji_docs.md | 208 +++++++++++++++++++++++++++++++++++- posts/saki_docs.md | 113 +++++++++++++++++++- static/images/og_faucet.png | Bin 5647 -> 5185 bytes 4 files changed, 320 insertions(+), 3 deletions(-) diff --git a/posts/meta.md b/posts/meta.md index e308ca0..cc39e1f 100644 --- a/posts/meta.md +++ b/posts/meta.md @@ -34,7 +34,7 @@ All the standard Markdown are supported (headers, bolds, italics, images, links, It also has a very cool warnings feature, which isn't used in this project, but can be seen in action if you use the [Makoto Web Editor](https://makoto.prussia.dev). ### Ryuji -Ryuji is a simple templating system that supports `if` statements, `for` loops, components, and inserting variables. It isn't quite as fully featured as Jinja/Nunjucks, but on the upside, Ryuji is around just 200 lines of code, and worked very well for my usecase. I think it's pretty cool. +Ryuji is a simple templating system that supports `if` statements, `for` loops, components, and inserting variables. It isn't quite as fully featured as Jinja/Nunjucks, but on the upside, Ryuji is less than 280 lines of code, and worked very well for my usecase. I think it's pretty cool. Here's a quick overview of the syntax: diff --git a/posts/ryuji_docs.md b/posts/ryuji_docs.md index d94355b..51585f8 100644 --- a/posts/ryuji_docs.md +++ b/posts/ryuji_docs.md @@ -1 +1,207 @@ -The Ryuji docs are under construction. Beep beep beep. +Ryuji is a templating language written in less than 280 lines of code. There are no dependencies besides the builtin Node.js module `fs`. If that is an issue (eg, running in a browser environment), it should be very straightforward to remove the dependency by deleting the `render_template` function and using the `render` function directory. + +# Syntax Docs + +Ryuji syntax is typically in the format `[[ something ]]` or `[[ some:thing ]]` (with more `:`s if necessary). The spaces matter! Specifically, Ryuji checks for syntax using the regex statement: `/\[\[ [a-zA-Z0-9.:\-_!]+ \]\]/g`. + +## Variables +```html +
Hi [[ employee.name ]],
+You may recently heard some distressing news about your colleague Dave.
+Please rest assured that these reports are false and exaggerated. Although he may be charged with [[ current_manslaughter_count ]] counts of manslaughter, we believe that these charges will be dismissed.
+[[ corporate_slogan ]]
+``` + +## Non-HTML Escaped Variables +```html +We don't think you should order the cheeseburger.
+[[ endif ]] +``` + +## If Not Comparison Statements +```html +[[ if:user.lactose_intolerant:user.vegan ]] +We don't think you should order the cheeseburger.
+[[ endif ]] +``` + +## Components +```html +[[ component:nav-bar ]] +Blah blah blah blah.
+``` + +*templates/components/navbar.html* + +```html + + +``` + +```html +[[ for trees:tree ]] + [[ component:tree-info ]] +[[ endfor ]] +``` + +*templates/components/tree-info.html* + +```html +Favourite song: [[ tree.favourite_song ]], Likes Dave: [[ tree.likes_dave ]]
+``` + +## Notes +- For loops can be nested. +- Components can have other components inside them (but there is a depth limit of 4 or 5 or 6 nested within each other, I forgot). + +# API/Library Docs + +## Class: Renderer + +### `constructor` +Creates an instance of the `Renderer` class. + +**Parameters:** +- `templates_dir` (`string`): Templates directory. +- `components_dir` (`string`): Components directory. +- `file_extension` (`\`.${string}\``, optional, default is `".html"`): File extension of templates. + +### `render` +Render a template given template contents and variables. + +**Parameters:** +- `template_contents` (`string`): Content of template. +- `vars` (`any`, optional but highly recommended): Dictionary/object of variables to render template with. +- `recursion_layer` (`number`, optional, defaults to `0`): Used internally to prevent infinite loops when templates circularly refer to each other. + +**Returns:** `string` (the rendered template) + +### `render_template` +Render a template given the template name. Basically, gets the contents of the template and then calls `render`. + +**Parameters:** +- `template_name` (`string`): The name of the template. +- `vars` (`any`, optional but highly recommended): Dictionary/object of variables to render template with. +- `recursion_layer` (`number`, optional, defaults to `0`): Used internally to prevent infinite loops when templates circularly refer to each other. + +**Returns:** `string` (the rendered template) + +### `remove_empty_lines` (static) +Removes empty lines from text. + +**Parameters:** +- `text` (`string`): Text to rid empty lines from. + +### `concat_path` (static) +Adds two paths together. Mostly intended for internal use only. + +**Parameters:** +- `path1` (`string`): First path. +- `path2` (`string`): Second path. + +**Returns:** `string` (`path1` added to `path2`) + +### `sanitize` (static) +Sanitizes text to make sure it cannot render as HTML. It replaces "<" with the HTML entity "&\lt;" and ">" with the HTML entity "&\gt;". Automatically done to + +**Parameters:** +- `non_html` (`string`): The text to sanitize. + +**Returns:** `string` (the sanitized text) + +### `check_var_name_legality` (static) +Checks to make sure a variable name is legal. Intended for internal use. + +**Parameters:** +- `var_name` (`string`): The variable name to check. +- `dot_allowed` (`boolean`, optional, default is `true`): Whether "." is allowed in the variable name. + +**Returns:** `boolean` (`true` is variable name is legal, `false` otherwise) + +### `get_var` (static) +Gets the value of a variable, errors if variable undefined. Intended for internal use. + +**Parameters:** +- `var_name` (`string`): Name of variable. +- `vars` (`any`, optional but highly recommended): Dictionary/object of variables to get value from. + +**Returns:** `any` (the value of the variable) + +### Properties +- `templates_dir`: `string` +- `components_dir`: `string` +- `file_extension`: `\`.${string}\`` (see in Types/Interfaces/Consts `file_extension`) + +## Types/Interfaces/Consts +These are exported, but there is no real use for them (outside of the module obviously), with the possible exception of writing an extension to Ryuji. Feel free to skip this section. + +- `const SYNTAX_REGEX`: Regex to search for Ryuji syntax. +- `type file_extension`: Typescript type `\`.${string}\``, that represents... file extensions. Shocker. +- `interface ForLoopInfo`: Used internally for Ryuji's for loops. + +# Usage Examples +Check Ryuji's [tests](https://github.com/jetstream0/hedgeblog/blob/master/tests.ts) for more examples. + +There is a real world example in [hedgeblog's code](https://github.com/jetstream0/hedgeblog). For a syntax example, look in the `templates` [directory](https://github.com/jetstream0/hedgeblog/tree/master/templates), or an API example in `saki.ts` and `index.ts`. diff --git a/posts/saki_docs.md b/posts/saki_docs.md index d47ed71..407867e 100644 --- a/posts/saki_docs.md +++ b/posts/saki_docs.md @@ -1 +1,112 @@ -The Ryuji docs are under construction. Vroom vroom. Oh no daves operating the bulldozer drunk again gimme a second +Saki is a very simple static build system, written in Typescript. There are no dependencies besides the builtin Node.js modules `fs` and `path`. + +# Class: Builder + +## `constructor` +Creates an instance of the `Builder` class. + +**Parameters:** +- `build_dir` (`string`, optional, default is `"/build"`): The directory to output the build to. + +## `copy_folder` (static) +Copies a directory to another directory. Used internally in `serve_static_folder`, there is probably no need to use this. + +**Parameters:** +- `folder_path` (`string`): Path to directory that should be copied. +- `dest_path` (`string`): Path to directory to copy to. + +## `serve_static_folder` +Adds a static folder to the build directory, meaning that it will be served. + +**Parameters:** +- `static_dir` (`string`): The path to the static directory to serve +- `dest_path` (`string`, optional, default is `"/"`): The path that the static directory should be served under. For example, if the static directory has a file "example.png", if the `dest_path` is `"/"`, the file will be written to `"/5LObkNQ7QGOMnQ-NoKRFi1d;czwT5`BLxW^yTz2~9*f6Le0?>&5! z#DTHTr?p8u^Z42)3h^yhOt?XVb5xbgNZTFq=_ri&1!D`!N`2qa_8anIAiL=P@Nm6_ zoycgr*wv4{ZW!A_vv5#=u8447%pUeB@=l@@OQ_|nhOSIR{jHHgO zO`8qc4uCjdn+KL**pC9V7SkeR<+raJBeXO#VKe@NIw|AKnmcB^p2c~hgLFup6kc#Z ztO9xjx|*sXIcJ5W*yD>c;kL{~hiQr=I{X1w)6aN3f~Td7zoz5R0Ixm|hZmExf_g$8 z0;^9NpPb60=M&=6@|so5%SSjKSrt57bQ2lk?)LUEr=N( 8r2><5CD7{frzKL;Z5Bvi!E$)5SpubQp$Oq(% zifOy<9D$vem&Dg|m&JCDN-x98uwPll2icuZ-^Zh_CEY(dbX9;Ydt=i2!JalhMF4M^ z>KuVD92*axkXa`Tb2!Qg9{iKL9}1tdY-u@1TRqUnIfYC0XTH#Rh+YTc9 L%a*= zX3@53>GcVdKSj&cMuEaJyK?1;35n!wa?0{2>RX4$?wiRBNv#ZjK&`e5=fKlAOgOk% zcGK019<~9EcaSfU*NF6W!SSmiKzv0p&;}miP+Xr>^{&Yl>3DZD8NzvB_}Yi_y$_PE z7pCm%{7$~U(D$YHtf3^$_)`QM4Iv_dSfVL1g e49WkacP#i!wC<<(j1grR|rpMIMcJutGibc==j;PnZPod924iJq $NS8na(Z3lZox95S*%h-8|nG=6z4%ZhSYep(+SWE)>oF~oe zVGPVgyw-Pj>?Q0EvHCEJAkxxV!#jqHCsOT9icEhjx#RywguEl$>?NkRh3*reqbR#I zw)1r*WLClj#nC&Lh45463E}_+;;q7ey916Srcb?u-J`H`w(duEJOOM7&*38G-Q=*8 z7&g|<@#Q}$ir*%INS*ge?&~DzhF2cgk_>r=Sr$QC^+?>{poXCeHCG_-?Fb@+4nLmd z7@8s<*O>GCns0a@A)Jy7>hF0JoICr(Nd-CQKD%V&g@I_!fTaWWJ3zQGPu81n7>_4_ zGU=e1EuSp3eVlP2Bw+7c93eP)W5sZSx;mM;0Q^>Vo4imo4t?jN`gWNQ64I9GWoo`V z91J~9;(F}h%1PZTd+y*wcoN!e*5D}S>t37RuSlPPGb6vvtjGn*$Ae%@$RF7T2!#eX zE{=!~47F Ao^8`0?8|pA25kzmdV`4U2|4;OdTr QH%&l&HbPndIfAJlr*(GDsYIH4nHtT#D zIQV`N`ZYqL_(rk)MLaae1dVq1W2C^UW<7kvq;xIj;C5hil0^2>6E58-^ERy6NpR9= ztsOYf8OKwm{c-nM)DE>c$iwPW$+Iep;|{A-dF4rdb&O*K>wyF_NAnl)lq(adn(vdm zvX_FTGnrbH0!nP|?HR6C %km|>iEkrH4byOi#QX*~vk3fXYZW~QrD<4^ zaAj*5J%@Hv!cDcr@6- c)6(EihpE9osSpotg6kbzU6!NNKp|+3JA*PG%(jCi6kZGY?;v< zl(VW$HMX{!E^3blNT71hUoQq*&QkXR;@41_n)@{qp-V3p;f JvTiCyB(Zgq+* zc~|rS!&1tqJiwrPOUJ_H` iJbR-+Y(20V}) zvHWro`a`Fhk+Fsi{R(=6K{)j@b W6qx-`FzG$0dQ;;q3NbuwrF~&cA3Cv@4R8RX;?QkdfvjO_A&p zTAG(W%{QRvSF&tM1W5?u-sy@hH0a82F!TjE*Z^T2?T=PADtrpV%Iwn!voq?3SuPrF zfd>>o&XITbN8S$dWnTfu10B-$b-ePO;1a*73=EL)2sy!s?EF|k;3LoRRKBa@%kbxC zWItf0NS2~;m&F1i$@WEjPE~jD?=-F!)ETks mea6QSZdm%&CQd*>9EqbZR`=aGG6=ok2e!8qNa+T|@ybFdGeCM#b zd-hD`Y-H@HUXnqTEc>=|l>8ij{J?vdFj+UTYkx^bECTO+#fOOhevH&FMF>ZPO65iP zIH$>fiTgfO&~H{AN2L@;DiA5b85iND&<`^fR&|vu7qs!B6FUds+WCi|Njv1%RmpEV zPqpXS2FrxQBxb(e_M36X5pR9s2)t_cISNt5+5L)7>Nv#Ptn*4lQALR(sFw#Prk*{l z@Dp3sgn0WUzVOsS9tYG>hD-f~Dmo}1XUKwZ;bHdEI+#iro)<# 4iA{kSUNWX18@Vwzm&8?`Qc_10Dw#{A>d0E)wUitx8<08uGL4Dz>pnPpBb5 zEn|}L+(AH1ajwwMrPt&Bg4V9r`E|uK%ew0aA>-mm*nqzvR1|4%F@|Qi7u7=KR8~?R z_E1-aZjj#G=Bu!0iL!6#iAc3n5jDZ @ja1Bjq}JxLJmP+Ir&@zZ_w{ug94@C~5Km3^doYFz%tR z7v=oPWK}Gr=;OK*08Di3I;?5t0;V!EL&IYXR#Q{b%}UWdF3#ox3`R@XovmL9ZY$7a zxsZhDR}wRUdG%$oviAfPeS%S?!2Do7tD7h;zRnnO)#R~?jViCU7Oa`}0fVBZV9I-6 z{=WN_J#usEOpL3#x 1mMh&rbYSLJPZse$+#b4WlYg!KC+1+3FxU8}ok23h)vjY Id5EZ0)S`qIz&@LKL2A&hAPY@Uw;jK#jjGwC829W?H1cT`GF}seBE`Vk5r~ zrxJGp?^UEk_v6o$W*lX`1~krPmunMBxuIrC>wBsVYbAc0!_#?(>2?Vqq#2*i@y(he z4F|GKqj&flCq73(X8|?zMfDj@k#z#!;%Kib3Cr)Rsk~$`UFr;xb`YCScX*0yW%))t zKs*>_^2fM|-aVTy2q8(owhm}Xe=FdJ^wf}avZeK@Weg72WylBXMpq+Mt{*VJJ*i+A zqV?y!@$o%)krH<6ZqxQ0gWRF>vq~Mh-@T4gd*2E)QnZx9RavZCb+dV)eTHMktI*iDM%qEnz$N%Yn2_JZnA+OI{v-;lf%c`zS+yYu5 zg_h&)AaZC-Bz|Q)L+zsB;|a8g=?pDIB4&aooJA#ix_v61S}t+=3ffWXYb_|(i5f8l z%*O}<5YwM7cCf9%+DO5oEvQSVOf{y<9@*v=9~s$K2u2Obot+xc*E5{dfU~^)nO3sR z%2l3|h r^ZZhX0Ei&_GCaEU+>r9UEgEWu088DTE#!3;lrqi^sJ^C|u6|e+11{FQ1C)wB zx1r2BHkbeGyAx4Wd-cylBoTr3E;5V*iCpD)jYz%w9jl;+WUWrXOUj{{W&7I_6R<=* zPGL9q8_!LUIKz%K15oX@n{S-N$gVOCRlSGNkgg=g^mX$8I8!!g7%!TQlW5!ytp) zx~*2H^1eq3N2@F&{3~{sH1k7;(%&;Yy9X8K*#W+h^>yGcOUe?ODJHocUrV=k94hKK z31?1hLAiuK?=q5}A@{>Tn>3gAKdp_+81-UwV 0Fi)VQElG8tJaxMWk8j z?uOlu_x);qGv8monR92JIrq#w_ndp4d+*#Rtv4!UBu_~2@bJjgR26mb@bFQ$ zThA3p7Kn#Oh^Gb6Rl2;q+}he|Y-~iM(Qr6?et!No0Efe2Fqp-~#rF30?(S|qJ-zbs z%Ja*MfPjEAELNivdAU%MsZAi1H>m59`KA;#e04?qu??S)`<7?x>{6v@oS>{lA7rtA zb!}nh7|=5>n>#95I1(^5&*|5skvER74Zpe?!eHQwS66rQ+p5F$B}PXS$Hq=iPh~qs z@VNXr3GnY_wkp<*d3lu#?;c%aad`OJYE4MIj cB*yaRfwE7d?qJ*5qw1on|9HKfI63%Px!zi+#}7 zWjHnA(m73F{>_K`&V!mCZ;kX9N 2qM|b2J}N7;c=dG#W^uOcperKU-ytQxGG5Os-X`9e`o8o%0tzm9fd@vEdilYy z8Wycs6grb7c&KNgq_3t#fLIR9BH&iPIxxPATTE?hgTpcKSFc~O1VlN|yfIplS;fPn z8c k;5< zf-GY@BH-fL4WZk}&5Dqh>_wKBtMZC=`UWk-Jb_4eE*CNIg*{Xr^uHl#M&7>z{s}oG zWe>Y^H&~83;vfP7xf@@A5O<^kNJQyx%PKim4z6?kO_?FXha#4FG}BqFk-eU1H*Y;F zKvlV8*5=xAJJll7V)m>mS}6POGNbe cCWM7Zoc*H;v<`$05i{BN#GZ+tihxmWmlwNq-)RY5{oe?TM zuZ#G!_P{%@5{ldRWq=r+FQ5vpE1&hcL~fpEK@^eRHqGO{$EDa2K3%!DDLWd3N<`Z$ zGgvbXZQrX4CV_T0S%*MIAje$Inbt%>xx1Hw&tsL+>0%R(3zo^?;Hpihue-BWyepFd zr0t4L?rLt&+vZ{dJSiaWcB@d-XPEV7i)%fa^w?)}myWxgI}oO(P1&YJoci3jbwc2y z@s|Et_pF66{9q15h5s#0KlNegz7QhNnYNXXX_^qRIcp%hiHqe4H}uknMh{13YI(-~syaa#)xQg} zb_1taW1CLi$mR%_vlX7cVtBE2zQdsKRO0+dpt0h`rMOfdTEMI=C7sV?%~WYlC92 DHIv7Jv1(!3}O{YGtT2js`bbvgw3xLVR zIeCX>rZ>-XZOA+kF86M2KUWX#18TVh7V I-y@aAVU&bFYwC@eD1(l8m3w56kKSNv<6lnE*-_5R<8vD77{CcqN zW4_;Jvi2S;n9AR@_^NN&*ZjjUFQAYk(IpzYA=79^cB3gnb6t9LHM7yNsg(Efm;FTW z5%WVBsgnoSZXgE=eE|_z@bTt2R=**fS(6R%;f_f7a<$ld>3x+t?TXz9T4Rr6+7(YK z^)>qpan>tmqo`yO)2Qs&?ezvvd4yUKo_63V(9CmawiB1M=15)Y|32EVIomjkyumV^ z;;ve@DNGrFh11~GdB%T F;&}PXHmOHEOMfU0;}ryI*vXu2++^xC@A#hfQ_TYmfj{Q0#F735um0VC}#(i zf1J-MXqL1&lp#8&W^-rFeBmW juK082pKA6T9$;aE_*A_ zxHX2f<<<)g-$u`9i?X{T@@n7j0wIN(A%HLau?fqotLjiGV4Oz%3t%0wI`bHM5+F(r z^36fygT;Xj)h{Ston5}+hKo$uNJm`gQL$`HFRDVhJG7E|V-iqLfD$o*tc-?gG(s>y z`Ab5&q68Vfq~%6{wHLP$`)eJ2;-XB4?*OtDAl0g`SCuCLPoIDPFYHMit%biSP+<~A# zAOGRLaYzIY-3SIcj=owmH)PebrHxi9pf<$(EUyiZ=thiFJB@HW6e#^5Ize@x$d;gj z4%i~;=wxkeZEpSrnb58dwen2XP8G2dckchpsc)mVX+=-8TtoCyRVyx#wK5h!PtWt< zu2rXMUb22_-cXE$8QFHUB0)+j@o#c7(`-QUPXZ!70v&qzM})a6w>x|lju|22-Z7>e z95}QpCwcx%ms=7|>ZA->5ZTevs%dxK`%qrNSx%{Hq!cM_UTR@ wa z&l>L4mNr+=%?iwc#6nwJLYNFxp3Ga4+~;h|X*_4i_+gHUkDfP 4r)7jCs&9 zQ4rSjz(m+nI)=I5pwqN;htp$@%s8unKEA#Ble63-6_VyLu=N22&RW_0pa*8l-51%l z_54tTG9=$ |QpwXDdbk%ziyHlOc$7c_d!RxG zQ`5|3m>-X!au$UKx74CYr@zPvC5lN>zV@L|gH^|@#tR3Z8+uZW!i}Yuy$MuYOtoK% zC-)Mb>oBv6X*%mBFb)3Df|6P`M2UEsHe~ZCg9LEo!MTrxw8$wbxY*}4mGCu+BfHo^ z4Vk)q@CGa4zj_bZQT-g_(_qTW5dUq#NZZI;Uu%Drv%!ovZE&e!+Woda8@S}jIIOHL zg2okf8ZCZ+@qQvbb)G-+%(vgXkH8H7&V6pLi@`?0S*u&Egu{i&5+grqN}~E!s@J4? zAOXW}DVng?87uM+Jh~GOilC$~R7en}A<4I^TM%S}aHkohhhNd`x-p<7AKifiy3o|D z &-+Pe6!PWSlLWsg$}>0^X6G$;8-#`Na0B8=UKr#uhE9JbJvkO> z@hF&Zo9}h|F&67faT&9{Ej=l*7f>*Gtl%eFmD3x&x{0yFV5-+lZj?`R&U3v_9%=YB zf6&D26xKt7%?{`T*M#r#QAN=uVE)wNl*)Sq4Ws2f7cJ_r=+`Q8sJ{0^msI*GH(DXY z-4?&|2Umb`#;)tL2H!PM+f*-e+9({?T^m+fnc3(yvR5`(bAld~am>1@D|Fbp4rHv2 zeR_R9@<&k4J^}e?o9~+GR-lRh_|a<8=jzc4(9O+_KX#Ml^JfcMlJ}MT#?;iT)KH#K zil=u!%CP0jTQNW3Htj$6Izb_iuTS};%uS9@%M%6-+ TM&$&o!}c$zoM#1k2dv2MS6f6 z;|B)^;kfzW%d#_+0DqYuTWc7%TMb~r>Aj2a&9b*dHgnZPW5XVmV*tkbWoR%Y1a$KB zA-$~ln?f!zmk-Fyn1kt8jAIK3 ~Jeow>p3`q+yQ*W$g4PTgF@H#*U1 zzB2x@9@>$BOoNTy(ozmXmyl=qCN7Nd*380~nC6=qoM_9<^Cu~TzBg9fq5w@DbVNid z*Qn#C$zM`CZYW $f|6As)h-|Ce$+qU8U+@By_)v1jz^#WI&MSa(N zL$(u9SuA$&bz6q|{HfnndlFzPdI8Dc52-BnMQTc7U!?T(%p(|&4iu$YQ|n)d2i@KT ztT*YI&5z$k_daxDJXo?09qT F8p2do|i_6Q46F>}*}1)@-vW#9d5YK5K*ZJd9G5@@9EC1=v;& zow%H=fqdH>Y(ttx6`qO$pWR1chorU=sKl&G?+?G1fT@o$aN(oOhZ}f~j*bjkV{@X! zk__Q%wO)cd!<=rxeV!F8&BquzK%AQe2`IgT8lm~=8_a1oQx6G)9-f!Kv`dFF0uNg2 zDs1%9<{UK;rKu#2>|_4uS%BEQSdLe ^Lugfld|;lff+f$Z_`z4l_*zIz z2v|AoDa??DE8ZirsF-ew JQjlUVN1OCtkh@#;G)O>NNJel z$VFI(cc4yGQB|IACQlEgYfw(3+gn@XS?AnjIXU21R|!bqM OMH sO7oxiBh zoWEXTgW^k$BnXKwu?1`M6W?DcBL(H0sVWuARiy|O+m^rX|LK@Q#q~Hm8EQM+Zxi*9 zHzAacv?Rj`T4l^W1&H}k^T%qjkXGV7rV_p9Y$gaN0hcUQ|I*izEF6Z 8HBCoD`u #}@Eh8AQ@maW;A%?$VKj)ntxp2MsQv_DDAo0TB9l132ka*QW zAf0?vW;)0nw>K3?KfCQIV!YyYh&_=gUoH&<9y$LIl(fB)KOT7lUYdR*(ufZV9!9C0 zk|bqiHTd{O=p%6TGB;OH_Xde($j$V~Ap^)q9pay!WmWlgbc(T*K$-~AI<46 Z{bkULaP{>-@=Ei(FYBBqDy`e;0$23k95a8%PkFuGiNV#^eq14I(rwr;xQp4cnI z!1R3|xr~B8N>ij-ZmOU*WY %y`l3Z4{k|cis0=7mj#Bt22>ua5dt1)Gi;HsiTORhc+~h$O$Y)HB zY}3Apc_SK}DJk9O#ek5u3D{!(gZ^>muLUr{QOz1a<%uwO_hI^J)W+;a@#$OQKNc<* z(VQkO91_4Jw;M@y4iU+ooICSlsT(b*>rbqbNhuUX`%Yl~RY3>>(O;+H8+9Yqq1CkJ zkl4BZHi^1cyvA~Xyh obnA U@5CYyH4vlm^ND< zL~JA+tIi%@FI~;sI7#kMO^HJK1b*_rn$&BTg1DXuy{FHzpRbbXsqxMgt+AHPI#l+} z*e$n^l@36GzPulKQNY?Zk(;%-d-JZ4D=X*yG;oL9<+x?-A}Q> _9O-a`YsB)gh9VO&^#!oLqoK_zUQ!CFGew<`M|_;2<7<^Q)5 n|1bV8{r_(%|D{A`