From cb09b5dc15d34df05eb51966dd627f9057bbc656 Mon Sep 17 00:00:00 2001 From: Fernando Macedo Date: Sat, 7 Mar 2026 23:31:50 -0300 Subject: [PATCH 1/2] feat: add --events option to diagram CLI for active state highlighting The diagram CLI now supports `--events event1 event2 ...` to instantiate the machine and send events before rendering, matching the Sphinx directive's `:events:` option. The pre-commit hook uses this to generate the README diagram with the green state active. --- .pre-commit-config.yaml | 1 + docs/diagram.md | 7 +++++++ docs/releases/3.1.0.md | 7 +++++++ statemachine/contrib/diagram/__init__.py | 21 ++++++++++++++++++--- tests/test_contrib_diagram.py | 18 ++++++++++++++++++ 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f6f9f6eb..692dfa2a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,6 +37,7 @@ repos: uv run python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine docs/images/readme_trafficlightmachine.png + --events cycle cycle cycle language: system pass_filenames: false files: >- diff --git a/docs/diagram.md b/docs/diagram.md index d607e339..6b09b1ba 100644 --- a/docs/diagram.md +++ b/docs/diagram.md @@ -103,6 +103,13 @@ The output format is inferred from the file extension: python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png ``` +To highlight the current state, use `--events` to instantiate the machine and +send events before rendering: + +```bash +python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png --events cycle cycle cycle +``` + ## Sphinx directive diff --git a/docs/releases/3.1.0.md b/docs/releases/3.1.0.md index 77c702b2..34c6a3f5 100644 --- a/docs/releases/3.1.0.md +++ b/docs/releases/3.1.0.md @@ -49,6 +49,13 @@ machine instance concurrently. This is now documented in the [#592](https://github.com/fgmacedo/python-statemachine/pull/592). +### Diagram CLI `--events` option + +The `python -m statemachine.contrib.diagram` command now accepts `--events` to +instantiate the machine and send events before rendering, highlighting the +current active state — matching the Sphinx directive's `:events:` option. +See {ref}`diagram:Command line` for details. + ### Bugfixes in 3.1.0 - Fixes silent misuse of `Event()` with multiple positional arguments. Passing more than one diff --git a/statemachine/contrib/diagram/__init__.py b/statemachine/contrib/diagram/__init__.py index 51e7bc78..a2e31a71 100644 --- a/statemachine/contrib/diagram/__init__.py +++ b/statemachine/contrib/diagram/__init__.py @@ -135,7 +135,7 @@ def import_sm(qualname): return smclass -def write_image(qualname, out): +def write_image(qualname, out, events=None): """ Given a `qualname`, that is the fully qualified dotted path to a StateMachine classes, imports the class and generates a dot graph using the `pydot` lib. @@ -143,10 +143,20 @@ def write_image(qualname, out): open/create and truncate such file and write on it a representation of the graph defined by the statemachine, in the format specified by the extension contained in the out path (out.ext). + + If `events` is provided, the machine is instantiated and each event is sent + before rendering, so the diagram highlights the current active state. """ smclass = import_sm(qualname) - graph = DotGraphMachine(smclass).get_graph() + if events: + machine = smclass() + for event_name in events: + machine.send(event_name) + else: + machine = smclass + + graph = DotGraphMachine(machine).get_graph() out_extension = out.rsplit(".", 1)[1] graph.write(out, format=out_extension) @@ -165,6 +175,11 @@ def main(argv=None): "out", help="File to generate the image using extension as the output format.", ) + parser.add_argument( + "--events", + nargs="+", + help="Instantiate the machine and send these events before rendering.", + ) args = parser.parse_args(argv) - write_image(qualname=args.class_path, out=args.out) + write_image(qualname=args.class_path, out=args.out, events=args.events) diff --git a/tests/test_contrib_diagram.py b/tests/test_contrib_diagram.py index 159d4a50..3d3a8152 100644 --- a/tests/test_contrib_diagram.py +++ b/tests/test_contrib_diagram.py @@ -136,6 +136,24 @@ def test_generate_complain_about_bad_sm_path(self, capsys, tmp_path): ] ) + def test_generate_image_with_events(self, tmp_path): + """CLI --events instantiates the machine and sends events before rendering.""" + out = tmp_path / "sm.png" + + main( + [ + "tests.examples.traffic_light_machine.TrafficLightMachine", + str(out), + "--events", + "cycle", + "cycle", + "cycle", + ] + ) + + assert out.exists() + assert out.stat().st_size > 0 + def test_generate_complain_about_module_without_sm(self, tmp_path): out = tmp_path / "sm.svg" From 3ddd8873ad2c258f3459767ea512ac82a7731d4f Mon Sep 17 00:00:00 2001 From: Fernando Macedo Date: Sat, 7 Mar 2026 23:35:44 -0300 Subject: [PATCH 2/2] fix: pre-commit generate-images regex and regenerate README diagram The YAML `>-` folding inserted a space in the `files:` regex, preventing the hook from matching `statemachine/contrib/diagram/` paths. Use a single-line string instead. Regenerate the README diagram with the green state highlighted. --- .pre-commit-config.yaml | 4 +--- docs/images/readme_trafficlightmachine.png | Bin 10201 -> 11895 bytes 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 692dfa2a..e70b8469 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,9 +40,7 @@ repos: --events cycle cycle cycle language: system pass_filenames: false - files: >- - (statemachine/contrib/diagram/ - |tests/examples/traffic_light_machine\.py) + files: (statemachine/contrib/diagram/|tests/examples/traffic_light_machine\.py) - id: pytest name: Pytest entry: uv run pytest -n auto --cov-fail-under=100 diff --git a/docs/images/readme_trafficlightmachine.png b/docs/images/readme_trafficlightmachine.png index 6519080d05f1413814bd1fde5b0beb79ff4e7021..5685ec100a85aaccb2bc8e59f59bb273831bdb42 100644 GIT binary patch literal 11895 zcmd6NWmJ}3w=POOg5m=TNT-0L(p`dpARr)}(%oH3BT^#WA}ydur-*d7v~(#U-FxzW z-?zs(=im9U$Jn=Hh(qOJ-D}Nt%`0Z8qP)~CEMhDa6qH*s(h^E2DAzu~pBoX^;dc&I zg%0?F@lsYw0_F1RS4Mqq33u zC%t@OKT$2nY=A9?H#9}cnXv@5DS6nKeBZjZ@=2SI4lWLi6j{DB<-@ok_TE{0QShcG@ z=i~(Wk)f=k43cKZJ%9dOUH#|btgr8%iSoU@z4mr#Lc;ijgnMV!a&m7A3oSQ{+vmBs zxGpX(hH{?CK6^GYI+`ZpD=c|>O@%C+5>a4?Vw+DU;PNpym!6iEGJsszOI=-^*I^Mp zhEO=NChBb8`gMYte6Wc8p9+`+Iu~ zGfWvC2`ZQ2^M?N<=Fc;3g40X-&%`@$&BC}Z%ge*XT-N=k3uyfI=QUmq##>g+^{e>LfO3i~cE zpT=dW>e;35g3?;LaFQ;EsR$q5K@^oibyQvPzNhFzJMnDEABReT(^{0i#<1B0}*G_Ti@ zoxFTRTpYp39LlY0WAG2r(zLg3-Kw;m4I;&SFCDJsC`i6_@Mls(L&HnBv8E>J-90nT zr0D4A)YR16+!Z*CTQ_eq+(3P0(LV_We(L-l3@fowJx`O zKR!y#@!3i|i5xlH>;E$MaQ{^K4uKe_s%lu+n?ec`tm5fU^1g3Pht~F5!U(r!8}-!G z;*-9in8}8De9X?qzkByCDe3Qp1w&ig+2#7*!lK9kMTYLKu3FbEU2*a6)3vTUUdAId z`_hhZKPbGmuf=ysA_nd`9i${46vkUlptcjwOBfL8`} z(ijbf{}-c>GtWBKCl~E;^~!*t9trFCeO@$lAf7G&mS@MC^5?&aTJe#)ju&J1!d=TYaE{^eikr zt*t@p8F|l@m6f%$WG_Ay6cps=frPZ4-8O=%nLXz zN_l&G)5VyZnMvmkg@=c)In`ulQpI!(kByCujBp*rM@QqP>E}1kzEk2Xk#bz_=5zmR zw5ylZiH8+PMMdShIVC1kD8-n%Y}Y5aw6uhvIxX+|qsEra|A941Hk;3qdncxZCLriG zh0jWFs?Yh!bF%jiNVKMvT_#4x0P##k23q>0LRe@~QBgsGg^sp@!fkpo41WWC{Z~VZ zyXkI^&Wh%xiAhK{*4Nu30an-(;kDjShIDp5^`nIEGh!!_{{&^r^5&~LGrY><$B&16 z6B81mhD`o>a&mH(mzOP~?@kQWk$a!-k7-p{WnL{#ns#n(uJP4tyG;KYu3lm}qHgI`ERp%F0?=T4G~k;{+QT z8@HHzg;RPkp4Drn8yUx@=X11eP(ZWJ9xB0ZKQG~>_km;|k?)Ws;I{n>8X!`<)zB+A zMNqb8mqKz;H!oP(Xm=40K9=glgZbfe;z(koHYHf<3^KGMOyq=QANXl|NedV z4v##tZDHr>)2E$qO7MGESGLjvT_q{0;BN0l1$ZCsUBeb59VKS{;VJp-`J zdH603i9n-~ieP47SXC0D&NoXc%*(TLaUsA8ys1N${vM4i0)GV8S#K%b*Vy~sYa}tZ zmAZ#V&3uKNvGLMtRLq0TX{P9_V!;(J(XB77tlXN(LBHO#F;=42=%b^eqN1(+Ha9FR z3=n#|6IcA?+lqT~VuJW`(T=yup>+KB#rXpdB`LD^@$oT4pUcYbUIqh#!ij3L z#tSCr44A2^s#aVQKh%E(rvNnzSQs1oqC_}Vb~FpBdueg;=lJ;kOohi}I0?_$`T6se z_2<}(NfWMHGjO!H;x0@Ch0YO=5k!pw-6Lq8BYcO5O~e<*}FNb8&H@E7?GO(f{*j`^xT)y_LPFyC8Y-JAQutBi?tIWrb2PpQNm~ zYcn!LQHxm?mzQg5YC0$LrLgi|Ce=DDb;6az6-QNNby078w{G{I<}_;wKmTye$-~_I z0iV}3c{|U8-_cLAS@1}6va?&}>{gxl)D3-lEaU# zXc!o}wJt@HLhDhCX=S>NK4c0B=<*rdc=y?pf9pki=&{Dq`^D z3;md%pI@)!P_3<|=Gx?k&YKF&-kz6~tNzVR{>KUmw*47m{us9`&hgXx+SUMIt{xtq z!;}3DsN}e=rJERru%xuKG+kZYBl_TGstw%DI1P?~fB@}U7dX&V=Il54%R38g2&yL{ z(-)h?Ds3M)gJvG@NO*X7n3ybQJ`QeQ(Z`SKFamhIJnr-L;N#E0*cRF}Xb$2^e-JGV z*BCfk8dP^;A`w3R3l)_B!dA8wz_VLq{O^-OJexeX>VxUoIXFfHdgjFBoX9Zx89crf z@a#E~zB1#KK;FN9Un+t$@u7jZrshPt@5S-2CV!xu{HeV!zi5i=MIt@+*I&#^VGpB+ zwK%w5J?VUobZN>O8i{Z+v9W={n@}7%>tZc4zkdIg%NjuVeH*WfQF+ueGCWKfkbpt# zJd)CC#D20L6Dlz>GSb@G`ud;es1TT3Sy@@*#R(G^*FJDP_Cz@?tz=4sS9$TLPoMJh zZ^)~N(G!u7L{b2yJlYX(`7MfguoO|W-ak0FKHDhb(_OopefI^qALZcC5Ks#?Btq`l zGjA`iC*i~+n3qJ`<&0e3g0fL#-N6KsW3plli_sO13Fyije zzyJ}K#ogm5R}GDEJ>T>FgE|8oQCl;CoK%$Rt1h&f}ST$Vw!Q|#*K~9BFof;hkle5){`zL zduzS6G;D0Prw4yFH#ebDC+a<25l$Dgc!Tu+V^+2gJ0W3+m5 z5m6uT`|?huHg&Wtty@igPMlfn&}52C`{FY$of%6M&gS{()Uf!9a=lsFu%;%lMx>g~a^Q!#f1*sX7etl+wu91;gtJ}s# zMs99ycD$s@%0oTLJPXHFrj9f;Sgs zTU+ajXXD}Ez{bKlIbKQkOgm~pmkgoY6S}BIFf3uyX4mB;Y5E)Z}j;@W9;@@;vyMlQUmg1f&hbk!f$r10(|4(Mv-CiJIK&p+R5U}BE%^1VEVOL6JFzdjO1$hO&GS)ueAIbi1W z$G6qWx5lVw)tFpdn$E`V+$MP^;W>h;jZK7u^wB3~szfuZvSV6O+<>^~%q7eibTy@W z=T2IEoa92+sTPk$G@pp*oKyMV4DuXZX{M?DRpdDTBXoDAk0yW?FI16XZ+CZhXGaWi zpN{Tn_F%c?7#^B|g#|xwU2H;DbuBFtBBD7$x8=U{MvxO&G`5M${Ijuv$;p$&I<@>? z7kg6$c22ddUp~AC2(P8(3HN1pxd;2^&9@N|n?^T_)+WX&{qj7v`-gp*#m&^ei&Q?n z*gK$)A8>RQM{wP3j1K*=_WJ9EZ^Q04ZqxZ2*2*!y*GCOHCOvw{QFy@)q$)IKHyTnH|&zl6PE>rgakAke*gTE znVFfYQQ9LPZq7-ZDo>Ai34aG~mGa0iNQobN(D}3B`e8}kDM~Kir-lqgMr4J$Qdf6? zxzWS*KBkZ-j}bC~_p!AQ`)z`O0Qe47$(F8CvGt0@z zy$K83ao2Kpua>6e2W~1ak5hfN&=z@qc9!?1VyV0u-o!aakC85h&vNt=4xwfElPsW_ z17@5Q6zKp6adB}oRrZD#XUCoE$nf5md|qD)IEABq?*~S8EaBa_JQPVQIx0OTyU}gb zotLvdQ5s+*fxLCUjviYrPOo8bSuPvxNi49dUXdv@hRmF{_U`VRA~v{4q#`g`5)xUY zguT5za1?Frso{L(bST;`wnikHJzyO;1z3-*$CJL6mTF!>k!cn#E^gJOKdV(}hn0l| zG_1>`g~;ab-#--<)e@NJ+JAp+?Cjh(W<$M*7T%%1y8l)0tM=ZRaafC!Rgq4^Q&X$ltqC(rPtZTWz)|6&Yc(_;tP)&(sF%&FVbno@Nkc=G`=Lh$FOOrqsms?=b3G7 z)A>@1F>Tz@Rj7^o#h6?Y=h{TM<+RpgC8a^YI|=FmAcihmGxaWiCcYT9`5es!BQoI! zOjkRu^k+V1iR$oMupE5goR+B|nnj3=-6(f9p4dS6=| zYP?Vf2M1`yNw>2*S9aY6XMpY8yO~}8VJ7_CVu1z0!cz3{<3}*1%6(+hpJer%_;$2ifcmxG`3&Bdeeg z`Sz`oy?tnCXwz)#j~`VvH5Ix+%(}|T3tL+v))QsJ!z!@7$Ga=aWfV!jt%aWeKLTdW z5){VG!$TqH_8HGn`>^a>o++$_-(j)+4L*~6y0G`UaWiDb34PSka$~eBD&A)osGFd>xJx=)d{>}2l?v68Qodop<)Dh)=Xi4J z8r?{hFKmoLLiKOnU?t{-7dwsI6Kh}Jc&?TvXVuix!y+I+qOh{O{MW7Skzs2XlAB?s3^+u8(kTh8_7MJO&71Y^M>J6Lr7UwIgn_t5A$UArSUx#D$2?v z5L|Y$c{mb@v~><2WP*_FJUm-7#Q%>`wsXq(-!@rm-(gwx&E=I9R}h_3*Ul3;Oj{!< zW(!fN8dFu)@6CA(Pm*2Ve*|h~Hf&2tNU4Q0xa}}D4q90KVBn4W7U>tec}AXDgs$!5 z<9xilKYGPRxsXQyeT_<_ihasx47~!YW*LHN@$8RDZ!kwbjufBn*-G+Y z6*eLhTHbO=i_;59Np%0Fhn$?WD{`s)T!MmvfPqcywBS`PIZOj3EibPnLsgWMjKI~1 z{P5ueH8r(IDdWhMkp!5S#X3s!LeA0H*chbUvuDpRbp1Qxl}3`}RD5T7GV&{W2L>LA zi0Ek)vi#rXU0kUu_WO|Uh7zM$pd6KcW@qEUn~(qesRGKPoIHPChH0F`?b&k78!cB@ z^^F;cHqMbzssQ2R#TeOmRx)9)D%l0Raw+O)PEO9L25%CFl+N`9L)!Q*CMG7?r%$o4 zus|~vO93u7UKZb|H{F`7yseg^Abk8$7X8H+u3+R-1%<=?{n)OhbJ9QcC26^Zj$Co? z|6cq}rz#{&PD*OOfrhO+(d;PU2 zGeMZd2M`JQDvY2zUq0fS87VU#>P_L>g{HFLu{b~fE;Y4iRE-f-)}Q5NI=oOjA@)a) z*1R-YftGr(eBS(8QNhZ{*xB7}_36_GOG}b)Qh8^IX-QbmjM@|7H=pi17%V;yhRFDrkOz+x9528;k{H__L*WpD{H* z3JMXE?u3Ilqc7^s!MF8loYtTd2a3t*xm!@Tu#x2YIX@a{hi*LP$w8dx%urtUKY6 zHY*PejlY@IBE4Rgr6ti#GI7fh=QmHy3+~iURn;H0%Sa$OiT{951-7;!Cx#Qu;k-T^ z%cKUvKRY}7$~y$TP*hUlYH$BKUSZb3cBbwaqytca+Z~Olw(k<;a>~n{EiBfdAx<*M zkJ99;cwC&hTU%R$u{NrM%zvMhlmw>+xk=dSOCgJA&Lut32*0Dv+AV1KA;GVHub7G0 zZ4lBeJaJWzx{lUVyRFq!jH=XtLiI5#>x!b51j5a9+thGaEqwh;(EUwFlG~% zaxA&`H$9iY+R6&dqL9N_x0Hhe2^3q6Vve!&N{qH zNJvjd_e4^1?m1o5JID$?@&zE{yUzc1v4Ni=+bU<+@BGKg*{X=>44j!s(p3&Ny@sjJ2L6G8=&ADQ zy!J04UVuX1XjycqPE93Cl^5~YGgII`R2`vlw&>wfQ&E9PAsd9hsj=}KJ?(=V{!g+% zM*}e3B4Rfh{3xTGBe(Xo^%4|}LMp#Am>LBIj3Oe9At51AQOHiuEDW9d(bB9sH37-C zR@|pqk;0l4R*CiX^?uaBIAp}UcKY!2RbIpm(>OnKM6uMB0baWT?DJ&{mX?-Q!NPSi z&*Q&Kp?B!zQ+VOUcSb6SI82-tG9UI+NjnnHLj*&wkOt}o43;0^0(l$-`NzI=IKg+w z$pQcWUPHxDB#Wo>=gfVcftAmGq$Ytc?U-~!K12mmTRj8P(`QfqEdBZZ{W~bZ*#>U` z4vv_<6=hHN$#Q@l4hh@^ zD1;;tH_-faz4pq8+yb;DpQoB$Lw9g=gzDBeH<#hww99=9bc6czZ_dyfSf_cQ^3N<5 z=I4`KAN^WcAtWKudj5RC@^pZ!rKJT@Ek6o)6tF;>}$5{U|1M2U0#xi+-#-65( z?}C!(5I_Ns6NK^F!NkSI=@uxugoFe$OUvM(An0y7tno~bG&Al(Fu4OY1Bnc^dB^gb|t0@F0`jGyW-5=6mTg zbsgR-o>gaEE*iw<6A6jcjSUD{%o_5sr5#^UBB<}*muy1Up1S6$t*yP`x6+s1W-Tft zB$TbavTi7HzIP{L_S-i~0fE~3mV&~|l(ARO-Xoo)GE%Kl;93(#hzZ>!~Z zT1BRXCfim`+N-IJ!QS3LyKb?7HjUBObM4-G)skY{L!+u7*d~5i)_65FwXl}?pdbuy z?{m0L0RdnHnfI@i$9 zaJV&_%TIbgudwjzmoNFn#V$@x-F^73TtTTB-~cONI+rzvf~A#I+@2-)ee*L!Gn&$ zK@elnPo6v}R4a+=FxrBCHd>^GE55(K51|gQjvum+c+b?jme{&r<-x#4h5|GgAbOzB zAfW(Zw+;S?Qa&dyuiJ8W;`P^*KzF&h51^@9$1OQjgGmBV_iM)uVk8K$|AJo*Sq}93 zP-KnoWrH?rqx}Mg_$u^H=)qSe%aAu}0T%4IB_!`xaZ{b!&Iie`Cz0f-2?-3bPp`Bn zG9W*HZK~RlO}nb!q^YWk7vh++rv$;xiEyq^=0IgGPBuz{f`S0(p+N|8Z~&d<1|yx4 zGNT0{{HqAv97jfz--GS%gY`5=^P=p%HWz@Ykv9;CH)Lh@(gv>56 zF3ttCGOW|n-F>>=lLu~WjBW+G5ZdJ?3c4Ld%TX?<<Qd$05^G=UsY=Q%krkWt5WfR=}9)^qI*IvSdaswx~A9@=wl zZBmHe2?@uR-vMsB!(I7jjsldqjEoEf$YKa^tImNMfP{rpf;kcJja(rO|NMD%Xc~p_ zCQO+m`1?P0a@s33?!vfv?;+$b@UA6VmBl^xp=&{wMuvySZud(R6Ehg3fiGA@h)l4r zA+tU`KBfwo@jleaSHUv|V&ZLRS zal~VOU9kcc%--D)0wjE9IP-pFVC_-&$Lfv8ENRzPgJEoaT6GW?SdV&H;yk^0}v{ zr^;m5iga!^I8MP5)L$wpAQt^sHU`1!dWZ)j4-X{vFIeMY59Vy-U$wp(1qdJrD>oaU z4p1~VUz@4-ywZT&RzF{raIk7syahCPCuWJ`q%0{J`1R{oMTXx{x?s~aJjl%XHwXH6 z#>&5`jAC|^Hzgw(d_53bK}aykp~}CU2tTGrB7n~!#s9^fqZT-ClRoeF?uugpCqf*d z)97;!jWLTqWfLNI=(`LI45A1Y_0RgldCyg((B9vMYa>h-NKmdqx_l!0eO}(G&!3T0SK|V4am#aaV$Rwb zgiSI`io~q(wXoTxkDr2t1;`D`<@D%?mzP&@UszBKDW3TdrjOthAW%7aN-7>Ap9?MA z?egLr?(lmwj1wrA3?rx&2o$Y-e1u0{GeBy4hlprxV?)-NGJ_48W#?Fbza2mpU>RtV z{9%fOii!iM>^K%JZsX4P2SGAuNtm#sk}$giJ~YTzxYa;E-v1EI9fE)g6Rwj{dlZaz z!G|j2VPqixH5DdM^glP#M*I?3ou#HmqmQ>~Cj{6wpqtK)cYAWL3^w2y=-B|?hkI*7 z%Z_N*o07OJY4K!|yLOMyKr6w-M|q_<@FB=FEG5*mFkFSsm6w~xtZCa6e0t81qD|Z7aL&~Bm(lx;tUldBcm{TYg)Vx&`Q98 zlDmK-LF`+O(=k1LmHE06;*j!whV})*AbJ+UexKu|cuXA9yicEgcQ-KnjGNznMMv|> z1QMzq7|GG>BMfVK`Q@GhHrn-SZF!%i`FT7*K93r{M7aZCZp)4dawg*9e)Ib48yg*K zb#;OPieP#e8fL-Z%ZiqU1|&>@lp&C@2Smlkk3pjp^C2N4`?IzdiuBl-N9X%0c~;bH zX{*I@g-|F6|k%|aCF*lY9$)Nc3bX$FWI|xPL=Rtr4#v+7OlmYDQ>_D1e8UPw2Z2gUUbdrXO zsiv>rm7Dd{yYFI9aeC~nGN~1Zw9G>;4XnIl$$>S#u%zU2b%4f?5=dve3lX39*%8de z>G@GY+yLG9N)IA`fND46)Py@UplXRznF*tg_R$bGMoRmuvkJOAS?{~cmzxxn0(}O+t#1a5X-8@*R8+TyZCUp+}E)fZ{pvPY(^nH0e9#SQ!{( zCMS~|Kcc1m*h~!*5}<-{j=voLI!SYRwg5C(^sVxK|~xxT)>&4#tRy}fFu@LKZ~=OIByd;8BicSjgeLV1kV7uc41m18C&5oFM_>*Y|jAQsn4r-FD_- zq7P2KkoSVSd850c-S&o`C_+PB9Yhr{5irOD6#vPg)*q^55d8YM&!VKFVq|6YR)Go- zSRLk5oaPx%!EA(J0_Obe^Wi>dYG}-rJe8H*O{Pj$#zr98dwVaS{tBL{>3q)gg8qo^v#vgH|SNCAZG ze!YV6A#kxEM~3#!Z&tp8et5t;`v0f7tD(X!)2mDh_V;#xKTEOUFY{V5t8~Xs8pg~Rx#G_@b0NK&q*(hne8u10H3Mu9c7@6(SH2D#T oy83^OSWlUw|9?(iS6!kaOmpp`%f+=|b{j=TQeL81T;K1%08nyiumAu6 literal 10201 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